UNR #4 Day1
A. 序列妙妙值
首先有简单 \(\mathcal O(n^2k)\) 暴力 DP,预计得分 \(40\) 分。
对于测试点 \(5 \sim 8\),\(a_i\) 很小,所以考虑利用值域相关的东西优化 DP。
设 \(f_{i, j}\) 表示前 \(i\) 个数分成 \(j\) 段的妙妙值。
记 \(V = \max a_i\)。开 \(V\) 个桶,以 \(s_x\) 为依据把 \(\min f_{x, j - 1}\) 存下来,然后枚举值转移,时间复杂度 \(\mathcal O(nkV)\),结合前面预计得分 \(80\) 分。
转移时的修改 — 查询是 \(\mathcal O(1) - \mathcal O(V)\) 的,考虑平衡一下变成 \(\mathcal O\left(\sqrt V\right) - \mathcal O\left(\sqrt V\right)\)。
拆成高八位和低八位,低八位在修改的时候就统计进桶里,把高八位存起来,在查询的再算就好。
时间复杂度 \(\mathcal O\left(nk\sqrt V\right)\)。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
constexpr int N = 6e4 + 10, K = 10, V = 1 << 8, mask = V - 1;
int n, k, a[N], s[N];
ll f[N][K], fm[V][V];
inline void chkmin(ll &x, ll y) {x = min(x, y);}
int main() {
ios_base::sync_with_stdio(0); cin.tie(nullptr), cout.tie(nullptr);
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> a[i], s[i] = s[i - 1] ^ a[i];
memset(f, 0x3f, sizeof(f)), f[0][0] = 0;
for (int j = 1; j <= k; j++) {
memset(fm, 0x3f, sizeof(fm));
for (int i = j; i <= n; i++) {
for (int w = 0; w < V; w++) chkmin(fm[s[i - 1] >> 8][w], f[i - 1][j - 1] + ((s[i - 1] & mask) ^ w));
for (int w = 0; w < V; w++) chkmin(f[i][j], fm[w][s[i] & mask] + ((w ^ (s[i] >> 8)) << 8));
}
}
for (int i = k; i <= n; i++) cout << f[i][k] << ' ';
return 0;
}
网络恢复
考虑树的做法。
相当于哈希。给每个点随一个 \([0, 2^{64} -1)\) 中的权值,然后一直找叶子,利用只有叶子度数为 \(1\) 的特性不断找叶子并删去,\(1\) 次 Query 即可。
进一步地,对于基环树中最后剩下来的那个环,我们可以随两个点,假定他们俩之间有边,如果真有就把这条边删去,直接破环成链了;如果没有的话就再随,随到的概率是 \(\dfrac2{L-1}\),其中 \(L\) 是环长,期望次数就是 \(\dfrac{L - 1}2\),也就是 \(\mathcal O(L)\) 的,也即我们可以用 \(\mathcal O(L)\) 的时间复杂度确定一个长度为 \(L\) 的环。同理,多个环也一样是线性的,很优。
把这个做法继续推广到一般情况,那就可以把边均分为 \(50\) 组,每组不断找度数为 \(1\) 的点并删去,在删无可删的时候随点并假设有边来破换成链,因为是随机图,所以就算算上相交的情况,环的个数和长度和也不会很大,可过。
代码:
#include "explore.h"
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
void Solve(int n, int m) {
mt19937_64 eng(19260817);
vector<ull> A(n), B; unordered_map<ull, int> vis;
for (int i = 0; i < n; i++) vis[A[i] = eng()] = i;
vector<int> all(m + 1);
iota(all.begin() + 1, all.end(), 1), shuffle(all.begin() + 1, all.end(), eng);
queue<int> q;
vector<int> S, R;
auto exist = [&](ull x) -> bool {return vis.find(x) != vis.end();};
auto report = [&](int u, int v) -> void {
Report(u + 1, v + 1);
if (exist(B[u] ^= A[v])) q.emplace(u);
if (exist(B[v] ^= A[u])) q.emplace(v);
};
auto solve = [&](int l, int r) -> void {
S.clear();
for (int i = l; i <= r; i++) S.emplace_back(all[i]);
B = Query(A, S);
for (int i = 0; i < n; i++) if (exist(B[i])) q.emplace(i);
while (1) {
if (!q.empty()) {
int u = q.front(); q.pop();
if (B[u]) report(u, vis[B[u]]);
} else {
R.clear();
for (int i = 0; i < n; i++) if (B[i]) R.emplace_back(i);
if (R.empty()) return;
while (1) {
int y = eng() % (R.size() - 1) + 1, x = eng() % y;
if (exist(B[R[x]] ^ A[R[y]]) || exist(B[R[y]] ^ A[R[x]])) {report(R[x], R[y]); break;}
}
}
}
};
int len = (m - 1) / 50 + 1;
for (int i = 1; i <= 50; i++) solve((i - 1) * len + 1, min(i * len, m));
}
C. 校园闲逛
记 \(V = \max v\)。
设 \(f_{u, v, s}\) 表示从点 \(u\) 到点 \(v\) 边权和为 \(s\) 的方案数。
类似 Floyd 枚举中间点朴素转移:
时间复杂度 \(\mathcal O(nmV)\)。
其实可以写得再简单一些:
其中 \(g_{u, v, w}\) 表示从 \(u\) 到 \(v\) 边权为 \(w\) 的边的条数。
这是个卷积形式啊,直接上 NTT 分治,时间复杂度 \(\mathcal O(n^3V\log^2 V)\)。
然后可以利用点值的性质,即可以先把所有的点值求出来,再 NTT,时间复杂度 \(\mathcal O(n^3V\log V + n^2V\log^2V)\)。
其实这时候在实现时控制好常数就能过了,但我是大常数选手,需要更优的算法。
找一些更牛的性质:因为分治时若 \(l \ne 0\) 则 \(2l > r\),所以 \(f_{u, v, 0} \sim f_{u, v, r - l}\) 都是做好了的,直接用它跟 \(f_{u, v, l} \sim f_{u, v, r}\) 做卷积就能得到正确的 \(f'_{u, v, l} \sim f'_{u, v, r}\),不需要再分治下去了。
那么时间复杂度的递归式就是 \(T(V) = T\left(\dfrac V2\right) + \mathcal O(n^3V + n^2V\log V)\),根据主定理,总的时间复杂度是 \(\mathcal O(n^3V + n^2V\log V)\)。
有 NTT,所以常数……一言难尽。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
constexpr int N = 10, M = 3e5 + 10, V = 1 << 17, MOD = 998244353;
int n, m, q, vm;
ull f[N][N][V], A[N][N][V], B[N][N][V], C[V], G[2][17];
struct Edge {
int u, v, w;
bool operator<(const Edge &rhs) const {return w < rhs.w;}
} e[M];
inline ull qp(ull base, int e) {
ull res = 1;
while (e) {
if (e & 1) res = res * base % MOD;
base = base * base % MOD;
e >>= 1;
}
return res;
}
namespace Poly {
int bits, len, rev[V];
inline void init(int n) {
len = 1, bits = -1;
while (len <= n) len <<= 1, bits++;
for (int i = 0; i < len; i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << bits);
}
void NTT(ull A[], bool I = 0) {
for (int i = 0; i < len; i++) if (i < rev[i]) swap(A[i], A[rev[i]]);
for (int i = 1; i < len; i <<= 1) {
ull wn = G[I][__builtin_ctz(i)];
for (int j = 0; j < len; j += (i << 1)) {
ull w = 1;
for (int k = j; k < j + i; k++) {
ull t = A[k + i] * w % MOD;
A[k + i] = (A[k] - t + MOD) % MOD, A[k] = (A[k] + t) % MOD;
w = w * wn % MOD;
}
}
}
}
void INTT(ull A[]) {
NTT(A, 1);
ull ilen = qp(len, MOD - 2);
for (int i = 0; i < len; i++) A[i] *= ilen;
}
}
void cdq(int l, int r) {
if (l == r) return;
if (l > 0) {
Poly::init((r - l) << 1); int len = Poly::len;
for (int u = 1; u <= n; u++) for (int v = 1; v <= n; v++) {
memset(A[u][v], 0, len << 3), memset(B[u][v], 0, len << 3);
for (int w = l; w <= r; w++) A[u][v][w - l] = f[u][v][w], B[u][v][w - l] = f[u][v][w - l];
Poly::NTT(A[u][v]), Poly::NTT(B[u][v]);
}
for (int u = 1; u <= n; u++) for (int v = 1; v <= n; v++) {
for (int w = 0; w < len; w++) {
C[w] = 0;
for (int k = 1; k <= n; k++) C[w] += A[u][k][w] * B[k][v][w];
C[w] %= MOD;
}
Poly::INTT(C);
for (int w = l; w <= r; w++) f[u][v][w] = C[w - l] % MOD;
}
return;
}
int mid = (l + r) >> 1;
cdq(l, mid);
Poly::init(r - l); int len = Poly::len;
for (int u = 1; u <= n; u++) for (int v = 1; v <= n; v++) {
memset(A[u][v], 0, len << 3), memset(B[u][v], 0, len << 3);
for (int w = l; w <= mid; w++) A[u][v][w - l] = f[u][v][w];
}
for (int i = 1; i <= m && e[i].w < len; i++) B[e[i].u][e[i].v][e[i].w]++;
for (int u = 1; u <= n; u++) for (int v = 1; v <= n; v++) {
Poly::NTT(A[u][v]), Poly::NTT(B[u][v]);
}
for (int u = 1; u <= n; u++) for (int v = 1; v <= n; v++) {
for (int w = 0; w < len; w++) {
C[w] = 0;
for (int k = 1; k <= n; k++) C[w] += A[u][k][w] * B[k][v][w];
C[w] %= MOD;
}
Poly::INTT(C);
for (int w = mid + 1; w <= r; w++) f[u][v][w] = (f[u][v][w] + C[w - l]) % MOD;
}
cdq(mid + 1, r);
}
int main() {
ios_base::sync_with_stdio(0); cin.tie(nullptr), cout.tie(nullptr);
for (int i = 0; i < 17; i++) G[0][i] = qp(3, (MOD - 1) / (1 << (i + 1))), G[1][i] = qp(G[0][i], MOD - 2);
cin >> n >> m >> q >> vm;
for (int i = 1, u, v, w; i <= m; i++) cin >> e[i].u >> e[i].v >> e[i].w;
sort(e + 1, e + m + 1);
for (int i = 1; i <= n; i++) f[i][i][0] = 1;
cdq(0, vm);
while (q--) {
static int x, y, v;
cin >> x >> y >> v;
cout << f[x][y][v] << '\n';
}
return 0;
}