Solution Set #1
带 * 的是写得不太详细的题解,慎看!
qoj15303. Basic Counting Practice Problems(计数dp,重链剖分,子树合并转单点插入,数学)
首先容易想到钦定所有 \(p_i>p_u\) 且 \(i\) 的祖先都小于等于 \(p_u\) 的这样的 \(i\),设这些 \(i\) 构成的集合是 \(S\),那么 \(S\) 需要满足不存在任意两个点是祖孙关系。则等价于钦定有 \(|S|\) 个数大于 \(p_u\),\(sz_u-\sum_{i\in S}{sz_i}-1\) 个数大于 \(p_u\)。
然后考虑怎么对这个东西 dp。
设 \(f_{u,x,y}\) 表示 \(u\) 的子树,已经选了 \(x\) 个数进 \(S\),其中 \(\sum_{i\in S}{sz_i}\) 等于 \(y\) 的方案数。
直接树上 dp 时间复杂度是 \(O(n^5)\),不能过。
这里可以用从后往前枚举 dfs 序的技巧将单次查询某个子树结果的复杂度降至 \(O(n^3)\),总复杂度是 \(O(n^4)\),过不了。
考虑改用 ABC311Ex 的技巧,先重链剖分,将子树合并变为单点插入,设 \(\text{dfs}(u,0/1)\) 表示目前 dp 数组是否被插入过。
每次先往重儿子递归,再往轻儿子递归,最后再用 \(O(n^2)\) 的代价插入当前节点 \(u\) 的信息。那么我们可以用 \(O(n^3)\) 的代价求出了所有与根在同一重链上点的答案,再递归求出所有这个重链的点的轻儿子子树内的结果即可。
分析一下复杂度:一个节点 \(u\) 对复杂度的贡献是 \(u\) 到根经过的所有不同的重链中,所有链顶 \(t\) 的 \(sz_t^2\) 之和。所以这个点的贡献数就是 \(O(n^2)+O(\frac{n^2}{4})+O(\frac{n^2}{16})+\ldots=O(n^2)\),总复杂度也就是 \(O(n^3)\)。
现在考虑得到了 dp 数组后怎么求答案。
容易发现如果钦定了有 \(x\) 个数小于 \(u\),\(y\) 个数大于 \(u\),则 \(p_u=k\) 的方案数是 \(\frac{(k-1)!}{(k-1-x)!}\cdot\frac{(n-k)!}{(n-k-y)!}\cdot (n-x-y)!=(k-1)!\cdot (n-k)!\cdot\binom{n-x-y}{k-1-x,n-k-y}\)。
直接枚举 \(k,x,y\) 是 \(O(n^4)\) 的,考虑套路性地将上面的 \(\binom{n-x-y}{k-1-x,n-k-y}\) 转成格路计数的结果,即 \((x+1,n-y)\) 走到 \((k,k)\) 的结果,那么将每个点的贡献加上后跑一遍格路计数即可。
时间复杂度:\(O(n^3)\)。
代码
#include <bits/stdc++.h>
// #define int int64_t
const int kMaxN = 705, kMod = 1e9 + 7;
int n;
int q[kMaxN], sz[kMaxN], wson[kMaxN], dep[kMaxN], res[kMaxN][kMaxN];
int fac[kMaxN], ifac[kMaxN];
int lim, f[kMaxN][kMaxN];
std::vector<int> tmp[kMaxN][kMaxN];
std::vector<int> G[kMaxN];
int qpow(int bs, int64_t idx = kMod - 2) {
int ret = 1;
for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
if (idx & 1)
ret = (int64_t)ret * bs % kMod;
return ret;
}
inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }
inline int C(int m, int n) {
if (m < n || m < 0 || n < 0) return 0;
return 1ll * fac[m] * ifac[n] % kMod * ifac[m - n] % kMod;
}
void prework(int n = 700) {
fac[0] = ifac[0] = 1;
for (int i = 1; i <= n; ++i) {
fac[i] = 1ll * i * fac[i - 1] % kMod;
ifac[i] = qpow(fac[i]);
}
}
void dfs1(int u, int fa) {
sz[u] = 1, dep[u] = dep[fa] + 1;
for (auto v : G[u]) {
if (v == fa) continue;
dfs1(v, u);
sz[u] += sz[v];
if (sz[v] > sz[wson[u]]) wson[u] = v;
}
}
void dfs2(int u, int fa, bool op) {
// std::cerr << u << ' ' << f.size() << ' ' << op << '\n';
for (int i = 0; i <= lim; ++i) {
for (int j = 0; j <= lim - i; ++j)
tmp[dep[u]][i][j] = f[i][j + i];
}
if (wson[u]) dfs2(wson[u], u, op);
for (auto v : G[u]) {
if (v == fa || v == wson[u]) continue;
dfs2(v, u, 1);
}
for (int i = lim; i; --i) {
for (int j = lim; j >= sz[u] + i - 1; --j)
inc(f[i][j], tmp[dep[u]][i - 1][j - sz[u] - (i - 1)]);
}
if (!op) {
static int dp[kMaxN][kMaxN];
memset(dp, 0, sizeof(dp));
// std::cerr << u << ' ' << sz[u] << ' ' << lim << '\n';
assert(lim >= sz[u]);
for (int i = 0; i <= sz[u]; ++i) {
for (int j = 0; j < sz[u]; ++j) {
if (!f[i][j]) continue;
inc(dp[n - i][sz[u] - j], 1ll * f[i][j] * q[sz[u] - j] % kMod);
}
}
for (int i = n; i; --i) {
for (int j = 1; j <= n; ++j) {
inc(dp[i][j], dp[i + 1][j]);
inc(dp[i][j], dp[i][j - 1]);
}
}
for (int k = 1; k <= n; ++k)
inc(res[u][k], 1ll * dp[k][k] * fac[k - 1] % kMod * fac[n - k] % kMod);
// for (int i = 0; i <= sz[u]; ++i) {
// for (int j = 0; j < sz[u]; ++j) {
// if (!f[i][j]) continue;
// assert(i < sz[u]);
// int coef = f[i][j];
// for (int k = 1; k <= n; ++k) {
// // inc(res[u][k], 1ll * f[i][j] * q[sz[u] - j] % kMod * C(k - 1, sz[u] - j - 1) % kMod * fac[sz[u] - j - 1] % kMod * C(n - k, i) % kMod * fac[i] % kMod * fac[n - (sz[u] - j) - i] % kMod);
// inc(res[u][k], 1ll * f[i][j] * q[sz[u] - j] % kMod * C(n - sz[u] + j - i, n - k - i) % kMod * fac[k - 1] % kMod * fac[n - k] % kMod);
// }
// }
// }
}
if (!op && u != wson[fa]) {
for (int lst = fa; u; lst = u, u = wson[u]) {
for (auto v : G[u]) {
if (v == lst || v == wson[u]) continue;
lim = sz[v];
for (int i = 0; i <= lim; ++i)
for (int j = 0; j <= lim; ++j)
f[i][j] = (i == 0 && j == 0);
dfs2(v, u, 0);
}
}
}
}
void dickdreamer() {
std::cin >> n; prework();
for (int i = 1; i <= n; ++i) std::cin >> q[i];
for (int i = 1; i < n; ++i) {
int u, v;
std::cin >> u >> v;
G[u].emplace_back(v), G[v].emplace_back(u);
}
dfs1(1, 0);
for (int i = 1; i <= n; ++i)
for (int j = 0; j <= n; ++j)
tmp[i][j].resize(n - j + 1);
// for (int i = 1; i <= n; ++i) {
// tmp[i].resize(n - i + 1);
// for (int j = 0; j <= n - i; ++j) tmp[i][j].resize(n - i + 1);
// }
lim = n, f[0][0] = 1;
dfs2(1, 0, 0);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j)
std::cout << res[i][j] << " \n"[j == n];
}
}
int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}
qoj10974. Edge Coloring Problem(图论,构造,adhoc,猜结论)
首先问题可以看成将这些边划分成最少的集合,使得每个集合内的边两两不共点。
容易得到一个答案的下界是 \(\displaystyle\left\lceil\frac{n(n-3)}{2\left\lfloor\frac{n}{2}\right\rfloor}\right\rceil\),当 \(n\) 是偶数时是 \(n-3\),\(n\) 是奇数时是 \(n-2\)。
由于两种奇偶性的情况需要分类讨论,考虑将 \(n\) 是奇数的情况转成偶数的情况。即当 \(n\) 是奇数时,就再加进去一个点 \(n+1\),并且找到一条原来不存在边的点对 \((x,y)(1\leq x<y\leq n)\),加入 \((x,y)\) 这条边,以及 \(\forall 1\leq z\leq n,z\neq x,z\neq y\) 中 \((z,n+1)\) 的边。
可以发现加边后每个点的度数一定还是点数减 \(3\),且答案的下界不变。
由于每个点的度数都是 \(n-3\),所以考虑把补图建出来。
则补图一定是由若干个环构成的,我们先考虑每个环都是偶环的情况,即补图可以二分图染色。
设 \(m=\frac{n}{2}\),则二分图左右每边有 \(m\) 个点,且两边内部构成一个团,所有连接两个颜色之间的边构成一个 \(m-2\)-正则二分图。
根据 Hall 定理,一个正则二分图一定存在完美匹配,所以可以先把这些跨颜色边构成的 \(m-2\) 个完美匹配求出来,再考虑两边内部的团。
这里需要分讨 \(m\) 的奇偶性。
-
\(m\) 是奇数。
先用一个比较典的技巧,将大小为 \(m\) 的团划分成 \(m\) 个大小为 \(\frac{m-1}{2}\) 的匹配,其中每组匹配都存在一个点不在任何一条边中。
我们就枚举这个不在匹配中的点 \(i\),将所有 \(j+k\equiv 2i\pmod m\) 且 \(j\neq k\) 的 \((j,k)\) 加进去即可,容易发现每条边所在的集合唯一,设左边的是 \(A_i\),右边的是 \(B_i\)。
然后取出任意一组跨颜色边的完美匹配,对于一条在这个完美匹配的边 \((u,v)\),往答案集合加入 \(A_u\cup B_v\cup\{(u,v)\}\) 即可。
剩余的跨颜色完美匹配每个分别加进去即可。
-
\(m\) 是偶数。
先用类似上面的技巧,将两边分别前 \(m-1\) 个点划分成 \(m-1\) 个大小为 \(\frac{m-2}{2}\) 的匹配,再把第 \(m\) 个点和每个匹配的未经过点之间的边加进去,即可将这个团划分成 \(m-1\) 个大小为 \(\frac{m}{2}\) 的完美匹配。
这里直接分别取左右的一组完美匹配拼起来,跨颜色边的完美匹配单独加进去即可。
最后考虑存在奇环的情况。所有偶环先正常二分图染色。
设有 \(2k\) 个奇环,则对于每个奇环先尽量二分图染色,最后会剩下一对相邻的点颜色相同,我们让第 \(i\) 个奇环的这样的点对颜色染成 \(i\bmod 2\),最后左右两边的点数就一样了。
由于直接不好构造,还是先调整成无奇环的情况。
设左边补图存在 \((u_1,u_2),(u_3,u_4),\ldots,(u_{2k-1},u_{2k-1})\) 这些边,右边有 \((v_1,v_2),(v_3,v_4),\ldots,(v_{2k-1},v_{2k-1})\)。
对于所有 \(1\leq i\leq k\),我们在原图上删掉 \((u_{2i-1},v_{2i-1})\) 和 \((u_{2i},v_{2i})\),再加上 \((u_{2i-1},u_{2i})\) 和 \((v_{2i-1},v_{2i})\),最后的图会变成和无奇环的情况完全一样。
我们只需要让所有新加入的边在一个匹配中,最后再把这些边同时变为删掉的边即可!
还是需要对 \(m\) 的奇偶性分类讨论。
-
\(m\) 是奇数。
找到跨颜色边中的一条边 \((x,y)\),使得 \(x\) 和 \(y\) 都不在 \(u_i\) 和 \(v_i\) 中。将 \((x,y)\) 所在的匹配作为取出的匹配,剩余的单独加入。
再通过 \(A_x\) 和 \(B_y\) 的初始值给整个数组重标号使得 \(A_x\) 包含所有 \(u\) 的边,\(B_y\) 包含所有 \(v\) 的边即可。
-
\(m\) 是偶数。
直接找到左右分别的任意一组完美匹配,对着这两个完美匹配重标号即可。
求出现在的所有匹配后再在结束后将被删掉的边复原即可。
由于要跑 \(n\) 次最大匹配,所以时间复杂度是 \(O(n^{3.5})/O(n^4)\),取决于写网络流还是匈牙利。
代码
#include <bits/stdc++.h>
// #define int int64_t
const int kMaxN = 305;
int n, _n, m, ring_cnt, res_cnt;
int col[kMaxN], res[kMaxN][kMaxN];
bool g[kMaxN][kMaxN], _g[kMaxN][kMaxN], ng[kMaxN][kMaxN], tag[kMaxN];
std::vector<int> ring[kMaxN], id[2], idd[2];
std::vector<std::array<int, 2>> ed[2], nbr[2][kMaxN], rese[kMaxN];
namespace Hungary {
int match[kMaxN];
bool vis[kMaxN];
bool dfs(int u) {
for (int v = 1; v <= n; ++v) {
if (vis[v] || col[v] == 0 || u == v || !ng[u][v]) continue;
vis[v] = 1;
if (!match[v] || dfs(match[v])) {
match[v] = u;
return 1;
}
}
return 0;
}
std::vector<std::vector<std::array<int, 2>>> getmatch() {
std::vector<std::vector<std::array<int, 2>>> ret;
for (int t = 1; t <= m - 2; ++t) {
std::vector<std::array<int, 2>> vec;
std::fill_n(match + 1, n, 0);
for (int i = 1; i <= n; ++i) {
if (col[i] == 1) continue;
std::fill_n(vis + 1, n, 0);
assert(dfs(i));
}
for (int i = 1; i <= n; ++i) {
if (col[i] == 1) {
assert(match[i]);
vec.push_back({match[i], i});
}
}
for (auto [u, v] : vec) assert(col[u] != col[v] && ng[u][v]), ng[u][v] = ng[v][u] = 0;
ret.emplace_back(vec);
// for (auto [u, v] : vec) std::cerr << u << ' ' << v << '\n';
// std::cerr << "-----------------\n";
}
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
if (col[i] == 0 && col[j] == 1)
assert(!ng[i][j]);
return ret;
}
} // namespace Hungary
void dickdreamer() {
std::cin >> n; _n = n;
for (int i = 1; i <= n; ++i) {
std::string str;
std::cin >> str;
for (int j = 1; j <= n; ++j)
g[i][j] = _g[i][j] = str[j - 1] - '0';
}
if (n % 2 == 1) {
int x = 0, y = 0;
for (int i = 1; i <= n; ++i)
for (int j = i + 1; j <= n; ++j)
if (!g[i][j])
x = i, y = j;
++n, g[x][y] = g[y][x] = 1;
for (int i = 1; i <= n; ++i)
if (i != n && i != x && i != y)
g[i][n] = g[n][i] = 1;
}
assert(n % 2 == 0);
m = n / 2;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
ng[i][j] = g[i][j];
memset(col, -1, sizeof(col));
memset(res, -1, sizeof(res));
int odd_cnt = 0;
for (int i = 1; i <= n; ++i) {
static bool vis[kMaxN] = {0};
if (vis[i]) continue;
++ring_cnt;
std::function<void(int)> dfs = [&] (int u) {
ring[ring_cnt].emplace_back(u);
vis[u] = 1;
for (int v = 1; v <= n; ++v) {
if (u == v || g[u][v] || vis[v]) continue;
dfs(v);
}
};
dfs(i);
// std::cerr << ring[ring_cnt].size() << '\n';
// for (auto x : ring[ring_cnt]) std::cerr << x << ' ';
// std::cerr << '\n';
// for (int i = 0; i < ring[ring_cnt].size(); ++i) assert(!g[ring[ring_cnt][i]][ring[ring_cnt][(i + 1) % ring[ring_cnt].size()]]);
if (ring[ring_cnt].size() % 2 == 0) {
for (int i = 0; i < ring[ring_cnt].size(); ++i) col[ring[ring_cnt][i]] = i % 2;
} else {
ed[odd_cnt % 2].push_back({ring[ring_cnt][0], ring[ring_cnt].back()});
for (int i = 0; i < ring[ring_cnt].size(); ++i) col[ring[ring_cnt][i]] = (odd_cnt + i) % 2;
++odd_cnt;
}
}
assert(odd_cnt % 2 == 0 && ed[0].size() == odd_cnt / 2 && ed[1].size() == odd_cnt / 2);
for (int i = 0; i < odd_cnt / 2; ++i) {
tag[ed[0][i][0]] = tag[ed[0][i][1]] = tag[ed[1][i][0]] = tag[ed[1][i][1]] = 1;
// std::cerr << ed[0][i][0] << ' ' << ed[0][i][1] << ' ' << ed[1][i][0] << ' ' << ed[1][i][1] << '\n';
assert(ng[ed[0][i][0]][ed[0][i][1]] == 0);
assert(ng[ed[1][i][0]][ed[1][i][1]] == 0);
assert(ng[ed[0][i][0]][ed[1][i][0]] == 1);
assert(ng[ed[0][i][1]][ed[1][i][1]] == 1);
ng[ed[0][i][0]][ed[0][i][1]] = ng[ed[0][i][1]][ed[0][i][0]] = 1;
ng[ed[1][i][0]][ed[1][i][1]] = ng[ed[1][i][1]][ed[1][i][0]] = 1;
ng[ed[0][i][0]][ed[1][i][0]] = ng[ed[1][i][0]][ed[0][i][0]] = 0;
ng[ed[0][i][1]][ed[1][i][1]] = ng[ed[1][i][1]][ed[0][i][1]] = 0;
}
bool ffll = 0;
int xx = 0, yy = 0;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
if (!tag[i] && !tag[j] && col[i] == 0 && col[j] == 1 && ng[i][j])
ffll = 1, xx = i, yy = j;
for (int i = 1; i <= n; ++i) {
id[col[i]].emplace_back(i);
if (tag[i]) idd[col[i]].emplace_back(i);
// std::cerr << col[i] << " \n"[i == n];
}
assert(id[0].size() == m && id[1].size() == m);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j)
if (i != j && col[i] == col[j])
assert(ng[i][j]);
}
// for (int i = 1; i <= n; ++i) {
// for (int j = 1; j <= n; ++j)
// std::cerr << ng[i][j];
// std::cerr << '\n';
// }
auto mat = Hungary::getmatch();
// std::cerr << mat.size() << '\n';
assert(mat.size() == m - 2);
for (auto &vec : mat) assert(vec.size() == m);
if (m % 2 == 0) {
static std::vector<std::array<int, 2>> tmp[kMaxN];
for (int i = 0; i < m - 1; ++i) {
tmp[i].push_back({i, m - 1});
for (int j = 0; j < m - 1; ++j) {
for (int k = j + 1; k < m - 1; ++k) {
if (j != k && (j + k) % (m - 1) == (2 * i) % (m - 1))
tmp[i].push_back({j, k});
}
}
// for (auto [u, v] : tmp[i]) std::cerr << u << ' ' << v << '\n';
// std::cerr << "--------------\n";
}
for (int o = 0; o < 2; ++o) {
static int p[kMaxN], pos[kMaxN];
static bool vis[kMaxN];
memset(p, -1, sizeof(p));
memset(pos, -1, sizeof(pos));
memset(vis, 0, sizeof(vis));
for (int i = 0; i < id[o].size(); ++i) pos[id[o][i]] = i;
for (int i = 0; i < ed[o].size(); ++i) {
vis[p[tmp[0][i][0]] = pos[ed[o][i][0]]] = 1;
vis[p[tmp[0][i][1]] = pos[ed[o][i][1]]] = 1;
}
for (int i = 0; i < m; ++i) {
if (p[i] != -1) continue;
for (int j = 0; j < m; ++j) {
if (!vis[j]) {
vis[p[i] = j] = 1;
break;
}
}
assert(p[i] != -1);
}
for (int i = 0; i < m - 1; ++i) {
for (auto [u, v] : tmp[i]) rese[i + 1].push_back({id[o][p[u]], id[o][p[v]]});
}
// std::cerr << o << " : ";
// for (int i = 0; i < m; ++i) std::cerr << p[i] << " \n"[i == m - 1];
}
for (int i = 0; i < m - 2; ++i) {
for (auto [u, v] : mat[i]) rese[i + m].push_back({u, v});
}
// for (int i = 1; i <= n - 3; ++i) {
// for (auto [u, v] : rese[i]) std::cerr << u << ' ' << v << '\n';
// std::cerr << "----------------\n";
// }
} else {
bool ffl = 0;
for (int i = 0; i < mat.size(); ++i) {
bool fl = 0;
for (int j = 0; j < mat[i].size(); ++j) {
if (!tag[mat[i][j][0]] && !tag[mat[i][j][1]]) {
fl = 1, std::swap(mat[i][j], mat[i][0]), std::swap(mat[i], mat[0]);
break;
}
}
if (fl) { ffl = 1; break; }
}
assert(ffl);
static std::vector<std::array<int, 2>> tmp[kMaxN], nbr[kMaxN];
for (int i = 0; i < m; ++i) {
for (int j = 0; j < m; ++j) {
for (int k = j + 1; k < m; ++k) {
if (j != k && (j + k) % m == (2 * i) % m)
tmp[i].push_back({j, k});
}
}
}
for (int o = 0; o < 2; ++o) {
static int p[kMaxN], pos[kMaxN];
static bool vis[kMaxN];
memset(p, -1, sizeof(p));
memset(pos, -1, sizeof(pos));
memset(vis, 0, sizeof(vis));
for (int i = 0; i < id[o].size(); ++i) pos[id[o][i]] = i;
vis[p[0] = pos[mat[0][0][o]]] = 1;
for (int i = 0; i < ed[o].size(); ++i) {
vis[p[tmp[0][i][0]] = pos[ed[o][i][0]]] = 1;
vis[p[tmp[0][i][1]] = pos[ed[o][i][1]]] = 1;
}
for (int i = 0; i < m; ++i) {
if (p[i] != -1) continue;
for (int j = 0; j < m; ++j) {
if (!vis[j]) {
vis[p[i] = j] = 1;
break;
}
}
assert(p[i] != -1);
}
for (int i = 0; i < m; ++i) {
for (auto [u, v] : tmp[i]) nbr[id[o][p[i]]].push_back({id[o][p[u]], id[o][p[v]]});
}
// std::cerr << o << " : ";
// for (int i = 0; i < m; ++i) std::cerr << p[i] << " \n"[i == m - 1];
}
// std::cerr << "------------\n";
for (int i = 0; i < m; ++i) {
for (auto [u, v] : nbr[mat[0][i][0]]) rese[i + 1].push_back({u, v});
for (auto [u, v] : nbr[mat[0][i][1]]) rese[i + 1].push_back({u, v});
// assert(g[mat[0][i][0]][mat[0][i][1]]);
rese[i + 1].push_back({mat[0][i][0], mat[0][i][1]});
// for (auto [u, v] : rese[i + 1]) std::cerr << u << ' ' << v << '\n';
// std::cerr << "------------\n";
}
for (int i = 1; i < mat.size(); ++i) {
for (auto [u, v] : mat[i]) rese[i + m].push_back({u, v});
}
}
for (int i = 1; i <= n - 3; ++i) {
for (auto [u, v] : rese[i]) assert(res[u][v] == -1), res[u][v] = res[v][u] = i;
}
for (int i = 0; i < odd_cnt / 2; ++i) {
assert(res[ed[0][i][0]][ed[0][i][1]] == res[ed[1][i][0]][ed[1][i][1]]);
res[ed[0][i][0]][ed[1][i][0]] = res[ed[1][i][0]][ed[0][i][0]] = res[ed[0][i][0]][ed[0][i][1]];
res[ed[0][i][1]][ed[1][i][1]] = res[ed[1][i][1]][ed[0][i][1]] = res[ed[0][i][0]][ed[0][i][1]];
// std::cerr << res[ed[0][i][0]][ed[0][i][1]] << '\n';
// std::cerr << res[ed[1][i][0]][ed[1][i][1]] << '\n';
}
std::cout << n - 3 << '\n';
for (int i = 1; i <= _n; ++i) {
for (int j = 1; j <= _n; ++j) {
if (_g[i][j]) assert(res[i][j] != -1), std::cout << res[i][j] << ' ';
else std::cout << -1 << ' ';
}
std::cout << '\n';
}
}
int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}
qoj10985. Perfect Suika Game on a Tree(贪心,猜结论,数据结构,可持久化线段树)
首先考虑给定一个局面怎么判断。
容易发现最终的操作方式都是本质相同的,因为我们可以每次找到所有点权最小值的点中深度最大的点,将其与父亲合并,如果父亲点权不是最小值,就一定不合法。
那么可以得到一个贪心:按照树的深度从深到浅做,假设 \(u\) 的子树已经做完了,轮到 \(u\) 的时候,如果 \(u\) 的儿子中存在和 \(a_u\) 相同的就合并,直到不能合并。
则有一个合法的必要条件是每个子树做完后剩下的点权两两不同且不能存在父亲的点权比儿子小的情况,否则祖先操作到上面的点后就不能消掉另一个了。
这说明每个子树做完后剩下的点权就是子树内 \(\sum 2^{a_i}\) 的所有为 \(1\) 的位从小到大排序的结果,设 \(S_u\) 表示 \(u\) 子树内点 \(2^{a_i}\) 的和,\(T_u=S_u-2^{a_u}\),可以得到充要条件:
- \(\text{popcount}(T_u)=\sum_{v\in\text{son}(u)}{\text{popcount}(S_v)}\),即儿子的子树剩下的点权两两不同。
- \(T_u\) 的最低位不小于 \(a_u\)。
满足这两个条件后显然就能够通过上面的贪心把 \(u\) 的子树消成按照 \(S_u\) 中为 \(1\) 的位按照从小到大排序的结构。
考虑怎么维护这件事情。
我们用可持久化线段树维护 \(S_u\) 和 \(T_u\) 的每个二进制位,合并子树时直接线段树合并,在合并的过程中维护进位即可。
最后修改由于只会交换相邻的节点,所以只会改变一个 \(S_u\) 和一个 \(T_v\),容易在线段树上维护。
时间复杂度:\(O(n\log n)\)。
代码
#include <bits/stdc++.h>
// #define int int64_t
const int kMaxN = 2e5 + 5, kMaxT = kMaxN * 300;
struct Node {
int ls, rs, cnt;
} t[kMaxT];
int n, m, q, sgt_cnt, cnt_wrong;
int u[kMaxN], v[kMaxN], a[kMaxN], dep[kMaxN], p[kMaxN];
int rt1[kMaxN], rt2[kMaxN], rrt1;
int sumc[kMaxN];
std::vector<int> G[kMaxN];
// rt1[u] : u 子树内的和,包括 u 自己
// rt2[u] : u 子树内的和,不包括 u 自己
// rrt1 : 全是 1 构成的树
void pushup(int x) {
t[x].cnt = t[t[x].ls].cnt + t[t[x].rs].cnt;
}
int build1(int l, int r) {
int nz = ++sgt_cnt;
t[nz].cnt = r - l + 1;
if (l == r) return nz;
int mid = (l + r) >> 1;
t[nz].ls = build1(l, mid), t[nz].rs = build1(mid + 1, r);
return nz;
}
int merge(int x, int y, int l, int r, std::vector<int> &vec) {
if (!x || !y) return x + y;
assert(t[x].cnt <= r - l + 1 && t[y].cnt <= r - l + 1);
int nz = ++sgt_cnt;
if (l == r) {
t[nz].cnt = t[x].cnt + t[y].cnt;
if (t[nz].cnt == 2) t[nz].cnt = 0, vec.emplace_back(l);
return nz;
}
int mid = (l + r) >> 1;
t[nz].ls = merge(t[x].ls, t[y].ls, l, mid, vec);
t[nz].rs = merge(t[x].rs, t[y].rs, mid + 1, r, vec);
pushup(nz);
return nz;
}
int cover(int x, int l, int r, int ql, int qr, int v, int p1 = rrt1) {
if (l > qr || r < ql) return x;
else if (l >= ql && r <= qr) return !v ? 0 : p1;
int nz = ++sgt_cnt, mid = (l + r) >> 1;
t[nz].ls = cover(t[x].ls, l, mid, ql, qr, v, t[p1].ls);
t[nz].rs = cover(t[x].rs, mid + 1, r, ql, qr, v, t[p1].rs);
pushup(nz);
return nz;
}
int getpos(int x, int l, int r, int ql, int qr, int v) {
if (l > qr || r < ql || !(!v ? (r - l + 1 - t[x].cnt) : t[x].cnt)) return qr + 1;
else if (l == r) return assert(t[x].cnt == v), l;
int mid = (l + r) >> 1, L = getpos(t[x].ls, l, mid, ql, qr, v);
if (L != qr + 1) return L;
else return getpos(t[x].rs, mid + 1, r, ql, qr, v);
}
int add(int x, int v) {
int p = getpos(x, 1, m, v, m, 0);
assert(p != m + 1);
x = cover(x, 1, m, v, p - 1, 0), x = cover(x, 1, m, p, p, 1);
return x;
}
int sub(int x, int v) {
int p = getpos(x, 1, m, v, m, 1);
assert(p != m + 1);
x = cover(x, 1, m, v, p - 1, 1), x = cover(x, 1, m, p, p, 0);
return x;
}
int merge(int x, int y) {
std::vector<int> vec;
int rt = merge(x, y, 1, m, vec);
for (auto v : vec) rt = add(rt, v + 1);
return rt;
}
void print(int x, int l, int r) {
if (l == r) {
std::cerr << t[x].cnt;
if (l == m) std::cerr << '\n';
return;
}
int mid = (l + r) >> 1;
print(t[x].ls, l, mid), print(t[x].rs, mid + 1, r);
}
void dfs(int u, int fa) {
p[u] = fa, dep[u] = dep[fa] + 1;
for (auto v : G[u]) {
if (v == fa) continue;
dfs(v, u);
rt2[u] = merge(rt2[u], rt1[v]);
}
rt1[u] = add(rt2[u], a[u]);
// std::cerr << u << " : ";
// print(rt1[u], 1, m);
// print(rt2[u], 1, m);
}
void ffix(int i, int v) {
if (!p[i]) return;
if (v == 1) sumc[p[i]] += t[rt1[i]].cnt;
else sumc[p[i]] -= t[rt1[i]].cnt;
}
void fix(int x, int v) {
// std::cerr << t[rt2[x]].cnt << ' ' << sumc[x] << '\n';
assert(t[rt2[x]].cnt <= sumc[x]);
cnt_wrong += v * (t[rt2[x]].cnt != sumc[x]);
cnt_wrong += v * (a[x] > getpos(rt2[x], 1, m, 1, m, 1));
// if (sonp[x].size()) cnt_wrong += v * (a[x] > *sonp[x].begin());
}
void work(int u, int v) {
if (dep[u] > dep[v]) std::swap(u, v);
fix(u, -1), fix(v, -1);
if (p[u]) fix(p[u], -1);
ffix(u, -1), ffix(v, -1);
rt1[v] = sub(rt1[v], a[v]), rt2[u] = sub(rt2[u], a[v]);
std::swap(a[u], a[v]);
rt1[v] = add(rt1[v], a[v]), rt2[u] = add(rt2[u], a[v]);
ffix(u, 1), ffix(v, 1);
fix(u, 1), fix(v, 1);
if (p[u]) fix(p[u], 1);
}
void dickdreamer() {
std::cin >> n; m = n + 2 * std::__lg(n);
for (int i = 1; i < n; ++i) {
std::cin >> u[i] >> v[i];
G[u[i]].emplace_back(v[i]), G[v[i]].emplace_back(u[i]);
}
for (int i = 1; i <= n; ++i) std::cin >> a[i];
rrt1 = build1(1, m);
// print(rrt1, 1, m);
dfs(1, 0);
// for (int i = 1; i <= n; ++i) rt1[1] = add(rt1[1], a[i]);
std::cin >> q;
// print(rt1[1], 1, m);
if (t[rt1[1]].cnt != 1) {
for (int i = 1; i <= q; ++i) std::cout << "No\n";
return;
}
for (int i = 2; i <= n; ++i) ffix(i, 1);
for (int i = 1; i <= n; ++i) fix(i, 1);
for (int i = 1; i <= q; ++i) {
int id, u, v;
std::cin >> id;
u = ::u[id], v = ::v[id];
work(u, v);
std::cout << (!cnt_wrong ? "Yes\n" : "No\n");
}
}
int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}
*qoj14727. Dolls 2(最优化dp,adhoc,猜结论)
首先设计状态去 dp 显然是不行的,因为状态数就是 \(O(n^2)\) 的,考虑找一些性质来简化状态。
经过手玩会发现一个性质:一定存在一种最优划分方案,使得每个段内是单调的。证明大概是先随便找到一组最优解再对其进行调整,具体见官方题解。
然后总共的可能区间数量就只有 \(O(n)\) 了,因为每个右端点最多只有 \(3\) 种可能的左端点。
先把可能的区间找出来,设 \(f_{i,0/1/2}\) 表示划分了 \([1,i]\),最后面的是第 \(0/1/2\) 个以 \(i\) 为右端点的区间,再直接 dp 即可。
时间复杂度:\(O(n)\)。
代码
#include <bits/stdc++.h>
// #define int int64_t
const int kMaxN = 1e6 + 5;
int n;
int a[kMaxN];
std::vector<int> vec[kMaxN], f[kMaxN];
inline void chkmax(int &x, int y) { x = (x > y ? x : y); }
inline void chkmin(int &x, int y) { x = (x < y ? x : y); }
void dickdreamer() {
std::cin >> n;
for (int i = 1; i <= n; ++i) vec[i].clear(), f[i].clear();
for (int i = 1; i <= n; ++i) std::cin >> a[i];
for (int i = 1, pre = 1; i <= n; ++i) {
if (i > 2) {
if ((a[i] > a[i - 1]) != (a[i - 1] > a[i - 2]))
pre = i - 1;
}
vec[i].emplace_back(i);
if (pre + 1 < i) vec[i].emplace_back(pre + 1);
if (pre < i) vec[i].emplace_back(pre);
f[i].resize(vec[i].size(), -1e9);
// std::cerr << i << ' ' << pre << '\n';
}
for (int i = 1; i <= n; ++i) {
for (int j = 0; j < vec[i].size(); ++j) {
int p = vec[i][j];
if (p == 1) {
f[i][j] = 1;
} else {
int nl = a[i], nr = a[p];
if (nl > nr) std::swap(nl, nr);
// if (i == 4) std::cerr << "fuck " << p << ' ' << nl << ' ' << nr << '\n';
for (int k = 0; k < vec[p - 1].size(); ++k) {
int l = a[p - 1], r = a[vec[p - 1][k]];
if (l > r) std::swap(l, r);
if (std::max(l, nl) <= std::min(r, nr)) chkmax(f[i][j], f[p - 1][k] + 1);
}
}
}
}
std::cout << n - *std::max_element(f[n].begin(), f[n].end()) << '\n';
}
int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}
uoj1013. Again Counting Stars(计数dp,容斥)
设 \(c_i\) 表示 \(i\to i+1\) 这条有向边被经过的次数,由于最终会回到原点,所以 \(i+1\to i\) 的次数也是 \(c_i\)。
一组 \(c_i\) 合法的条件是所有 \(c_i\neq 0\) 的位置构成一段区间,且 \(p\) 或者 \(p-1\) 在这个区间内。
最终 \(a_i\) 的变化量 \(\Delta_i=b_i-a_i\) 就等于 \(c_{i-1}-c_i\),对 \(\Delta_i\) 求个前缀和就是 \(-c_i\),再推导一下,可以得到求全局 \(c_i\neq 0\) 的方案等价于下面这个问题:
求 \(d_i\) 的数量,满足:
- \(d_i\geq d_{i-1},d_1\geq 0\).
- \(d_i\leq \sum_{j=1}^{i}{a_j}-1\).
设 \(s_i=\sum_{j=1}^{i}{a_j}\),则需要满足 \(d_i\leq s_i-1\)。
由于直接做十分复杂,考虑容斥掉 \(d_i\geq d_{i-1}\) 这个限制。
假设已经钦定 \(d_l>d_{l+1}>\ldots>d_{r}\),由于 \(d_l\leq s_l-1\),所以 \([l,r]\) 这个段的贡献就是 \(\binom{s_l}{r-l+1}\),对这个进行 dp 即可做到单次 \(O(n^2)\)。
原问题就枚举钦定 \(c_i\neq 0\) 的左端点再进行 dp 就是 \(O(n^3)\) 了。
考虑优化(注:后面的 \(s_i\) 是原数组 \(a_i\) 的前缀和)。
不妨设钦定 \(c_i\neq 0\) 的区间是 \([l,r]\),容易发现 dp 是无法再进行优化的,考虑优化掉枚举左端点 \(l\) 这个东西。
注意到直接对 \(s_i\) 跑上面那个 dp 只有当 \(d_l<s_{l-1}\) 会出现问题,所以直接在 dp 的过程中减去钦定的第一段的左端点 \(l'\) 满足 \(d_{l'}<s_{l'-1}\) 的方案数即可,和上面的过程是非常类似的。
求答案需要正着 dp 一遍再反着 dp 一遍维护差分数组,具体细节见代码。
时间复杂度:\(O(n^2)\)。
代码
#include <bits/stdc++.h>
// #define int int64_t
const int kMaxN = 8e3 + 5, kMod = 998244353;
int n;
int a[kMaxN], s[kMaxN], fac[kMaxN], ifac[kMaxN], inv[kMaxN], res[kMaxN];
int C[kMaxN][kMaxN], f[kMaxN], g[kMaxN];
int qpow(int bs, int64_t idx = kMod - 2) {
int ret = 1;
for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
if (idx & 1)
ret = (int64_t)ret * bs % kMod;
return ret;
}
inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }
inline int getop(int x) { return (~x & 1) ? 1 : (kMod - 1); }
void prework(int n = 8e3) {
fac[0] = ifac[0] = 1;
for (int i = 1; i <= n; ++i) {
fac[i] = 1ll * i * fac[i - 1] % kMod;
ifac[i] = qpow(fac[i]);
inv[i] = qpow(i);
}
}
void dickdreamer() {
std::cin >> n;
for (int i = 1; i <= n; ++i) {
std::cin >> a[i];
s[i] = add(s[i - 1], a[i]);
C[i][0] = 1;
for (int j = 1; j <= n - i + 1; ++j)
C[i][j] = 1ll * C[i][j - 1] * (s[i] - j + 1) % kMod * inv[j] % kMod;
}
inc(res[1], 1);
for (int i = 1; i < n; ++i) {
f[i] = 0;
for (int j = 1; j <= i; ++j) {
inc(f[i], 1ll * C[j][i - j + 1] * getop(i - j) % kMod * (f[j - 1] + 1) % kMod);
dec(f[i], 1ll * C[j - 1][i - j + 1] * getop(i - j) % kMod);
}
dec(res[i + 2], f[i]);
}
for (int i = n - 1; i; --i) {
g[i] = 0;
for (int j = i; j < n; ++j) {
inc(g[i], 1ll * C[i][j - i + 1] * getop(j - i) % kMod * (g[j + 1] + 1) % kMod);
dec(res[i], 1ll * C[i - 1][j - i + 1] * getop(j - i) % kMod * (g[j + 1] + 1) % kMod);
}
inc(res[i], g[i]);
}
// for (int l = 1; l < n; ++l) {
// static int f[kMaxN];
// memset(f, 0, sizeof(f));
// f[l - 1] = 1;
// for (int i = l, s = 0; i < n; ++i) {
// s += a[i];
// if (s <= 0) continue;
// int comb = f[i - 1];
// for (int j = i; j <= n; ++j) {
// comb = 1ll * comb * (s - j + i) % kMod * inv[j - i + 1] % kMod;
// inc(f[j], 1ll * getop(j - i) * comb % kMod);
// }
// inc(res[l], f[i]);
// dec(res[i + 2], f[i]);
// }
// }
for (int i = 1; i <= n; ++i) {
inc(res[i], res[i - 1]);
std::cout << res[i] << " \n"[i == n];
}
}
int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
prework();
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}
uoj1016. 成王败寇(字符串,数据结构,ac自动机,根号分治,分块)
首先建出 fail 树,那个 \(s_x\) 是 \(s_u\) 的祖先等价于 \(x\) 在 fail 树上是 \(u\) 的祖先。
然后考虑只有一个限制条件 \((a,c)\) 的情况。
直接做需要特判长度小于 \(a\) 的情况,不妨用总数减去不合法数。
有一个暴力的想法是枚举原树上所有深度是 \(a\) 的点,找到所有父亲的颜色不等于 \(c\) 的点,称这些点是关键点,那么不合法的一定在关键点的子树内。
乍一看会发现这个十分难做,因为这些关键点数的量级不好确定。但是实际上有用的关键点只有 \(O(\sqrt n)\) 个!
考虑构造性证明,定义一个阈值 \(B\),暴力找到 \(s_u\) 的祖先长度在 \([a,a+B]\) 的所有点,这些点在深度为 \(a\) 处的关键点总共只有 \(O(B)\) 个。对于所有 \(s_u\) 长度大于 \(a+B\) 的祖先,它们到深度为 \(a\) 的关键点的路径长度至少是 \(B\),所以这些关键点的子树大小都不小于 \(B\),又因为同层的点的子树两两不交,所以这种情况对关键点数量的贡献是 \(O(\frac{n}{B})\)。总数是 \(O(B+\frac{n}{B})\),取 \(B=\sqrt n\) 时就是 \(O(\sqrt n)\) 了!
模拟上面的构造性证明即可找到所有 \(O(\sqrt n)\) 个关键点。
如果有多组条件就先对每组条件求出关键点,然后归并即可。
求答案时直接在 fail 树上 dfs,在 dfs 的过程中用一个数据结构维护当前点祖先在原树的 dfs 序,查询时需要求 \(O(q\sqrt n)\) 次区间和,用 \(O(\sqrt n)-O(1)\) 的分块即可。
时间复杂度:\(O((n+q)\sqrt n)\)。
代码
#include <bits/stdc++.h>
// #define int int64_t
const int kMaxN = 2e5 + 5;
struct Query {
int u, k, a[3], c[3];
} qq[kMaxN];
int n, q, B;
int p[kMaxN], w[kMaxN], trie[kMaxN][2], cnt[kMaxN], fail[kMaxN], failp[kMaxN][20];
int dfn_cnt, dep[kMaxN], sz[kMaxN], dfn[kMaxN], L[kMaxN], R[kMaxN];
int res[kMaxN];
std::vector<int> G[kMaxN], T[kMaxN], sid[kMaxN], qr[kMaxN];
std::vector<int> rt[kMaxN][3], tmp[kMaxN][3];
std::vector<std::pair<int, int>> tid[kMaxN];
struct BIT {
int c[kMaxN];
void upd(int x, int v) {
for (; x <= n; x += x & -x) c[x] += v;
}
int qry(int x) {
int ret = 0;
for (; x; x -= x & -x) ret += c[x];
return ret;
}
int qry(int l, int r) { return l <= r ? qry(r) - qry(l - 1) : 0; }
} bit;
struct Block {
int b, tot, pre[kMaxN], preb[kMaxN], L[kMaxN], R[kMaxN], bel[kMaxN];
void prework() {
b = sqrtl(n), tot = (n + b - 1) / b;
for (int i = 1; i <= tot; ++i) {
pre[i] = preb[i] = 0;
L[i] = (i - 1) * b + 1, R[i] = std::min(i * b, n);
for (int j = L[i]; j <= R[i]; ++j) bel[j] = i;
}
}
void upd(int x, int v) {
for (int j = x; j <= R[bel[x]]; ++j) pre[j] += v;
for (int j = bel[x]; j <= tot; ++j) preb[j] += v;
}
int qry(int l, int r) {
if (l > r) return 0;
else if (bel[l] == bel[r]) return pre[r] - (l == L[bel[l]] ? 0 : pre[l - 1]);
else return pre[R[bel[l]]] - (l == L[bel[l]] ? 0 : pre[l - 1]) + pre[r] + preb[bel[r] - 1] - preb[bel[l]];
}
} bl;
void build() {
for (int i = 1; i <= n; ++i) {
trie[i - 1][0] = std::max(trie[i][0] - 1, 0);
trie[i - 1][1] = std::max(trie[i][1] - 1, 0);
}
std::queue<int> q;
for (int i = 0; i < 2; ++i)
if (trie[0][i])
q.emplace(trie[0][i]);
for (; !q.empty();) {
int u = q.front(); q.pop();
for (int i = 0; i < 2; ++i) {
if (trie[u][i]) {
fail[trie[u][i]] = trie[fail[u]][i];
q.emplace(trie[u][i]);
} else {
trie[u][i] = trie[fail[u]][i];
}
}
}
for (int i = n; i; --i) fail[i] = fail[i - 1] + 1;
fail[1] = 0;
for (int i = 2; i <= n; ++i) {
T[fail[i]].emplace_back(i);
failp[i][0] = fail[i];
}
for (int i = 1; i <= std::__lg(n); ++i)
for (int j = 1; j <= n; ++j)
failp[j][i] = failp[failp[j][i - 1]][i - 1];
std::function<void(int)> getcnt = [&] (int u) {
++cnt[u];
for (auto v : T[u]) {
cnt[v] = cnt[u];
getcnt(v);
}
};
getcnt(1);
}
int getfa(int x, int k) {
if (dep[x] <= k) return x;
for (int i = std::__lg(n); ~i; --i)
if (dep[failp[x][i]] > k)
x = failp[x][i];
return fail[x];
}
void dfs1(int u) {
sz[u] = 1, dfn[u] = ++dfn_cnt;
for (auto v : G[u]) {
dep[v] = dep[u] + 1;
dfs1(v);
sz[u] += sz[v];
}
L[u] = dfn[u], R[u] = dfn[u] + sz[u] - 1;
if (sz[u] >= B) sid[dep[u]].emplace_back(u);
}
void work(int x) {
int u = qq[x].u, k = qq[x].k;
int a[k] = {0}, c[k] = {0};
for (int i = 0; i < k; ++i) a[i] = qq[x].a[i], c[i] = qq[x].c[i];
for (int i = 0; i < k; ++i) {
for (auto id : sid[a[i]]) {
assert(dep[id] == a[i]);
if (w[id] == c[i]) rt[x][i].emplace_back(id);
}
int now = getfa(u, a[i] + B);
for (int j = now; dep[j] >= a[i]; j = fail[j]) tid[j].emplace_back(x, i);
}
}
void dfs2(int u) {
static int top = 0, stk[kMaxN] = {0};
stk[dep[u]] = u;
for (auto [x, i] : tid[u]) {
if (w[stk[qq[x].a[i]]] == qq[x].c[i] && (!tmp[x][i].size() || stk[qq[x].a[i]] != tmp[x][i].back()))
tmp[x][i].emplace_back(stk[qq[x].a[i]]);
}
for (auto v : G[u]) dfs2(v);
}
bool check(int x, int y) { return L[x] <= dfn[y] && dfn[y] <= R[x]; }
void merge(std::vector<int> &a, std::vector<int> &b) {
std::vector<int> tmp;
tmp.swap(a);
// std::cerr << "!!! " << tmp.size() << ' ' << b.size() << '\n';
for (int i = 0, j = 0; i < tmp.size() || j < b.size();) {
if (i < tmp.size() && j < b.size()) {
for (; i < tmp.size() && j < b.size() && check(tmp[i], b[j]); ++j) {}
for (; i < tmp.size() && j < b.size() && check(b[j], tmp[i]); ++i) {}
}
if (j == b.size() || i < tmp.size() && dfn[tmp[i]] < dfn[b[j]]) a.emplace_back(tmp[i++]);
else if (i == tmp.size() || j < b.size() && dfn[b[j]] < dfn[tmp[i]]) a.emplace_back(b[j++]);
else assert(tmp[i] == b[j]), a.emplace_back(tmp[i++]), ++j;
}
// std::cerr << "???\n";
}
void dfs3(int u) {
// bit.upd(dfn[u], 1);
bl.upd(dfn[u], 1);
for (auto id : qr[u]) {
int k = qq[id].k;
int lst = 0;
// for (auto i : rt[id][0]) res[id] -= bit.qry(L[i], R[i]), assert(L[i] > lst), lst = R[i];
for (auto i : rt[id][0]) res[id] -= bl.qry(L[i], R[i]), assert(L[i] > lst), lst = R[i];
}
for (auto v : T[u]) dfs3(v);
// bit.upd(dfn[u], -1);
bl.upd(dfn[u], -1);
}
void dickdreamer() {
std::cin >> n; B = sqrtl(n);
for (int i = 2; i <= n; ++i) {
std::cin >> p[i] >> w[i];
G[p[i]].emplace_back(i), trie[p[i]][w[i]] = i;
}
build();
dfs1(1);
std::cin >> q;
for (int i = 1; i <= q; ++i) {
std::cin >> qq[i].u >> qq[i].k;
res[i] = cnt[qq[i].u];
for (int j = 0; j < qq[i].k; ++j) std::cin >> qq[i].a[j] >> qq[i].c[j], qq[i].c[j] ^= 1;
qr[qq[i].u].emplace_back(i);
work(i);
}
dfs2(1);
for (int i = 1; i <= q; ++i) {
for (int j = 0; j < qq[i].k; ++j) {
merge(rt[i][j], tmp[i][j]);
if (j) merge(rt[i][0], rt[i][j]);
}
}
bl.prework();
dfs3(1);
for (int i = 1; i <= q; ++i) std::cout << res[i] << '\n';
// for (int i = 2; i <= n; ++i) std::cerr << fail[i] << " \n"[i == n];
}
int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}
agc076a. Hamming-Distant Arrays(计数,dp,adhoc,猜结论)
先用总方案数减去不合法的方案数。
不合法的方案就是说存在一个序列 \(y\),使得 \(y\) 和所有序列 \(x_i\) 的交都 \(\geq n+1\)。
考虑一组方案不合法的充要条件是什么。
容易找到一组必要条件是每列的众数出现次数之和要大于等于 \(n(n+1)\),下面证明这个也是必要的。
不妨设 \(c_i\) 是第 \(i\) 列的众数出现次数,那么找到所有 \(c_i>1\) 的所有 \(i\),且只保留至多前 \(n\) 个,设保留下来的是 \(i_1,i_2,\ldots,i_k\)。
那么让所有 \(i_j\) 列选择该列的众数,则目前每行的交都不超过 \(n\),且总和是 \(\sum c_{i_j}\geq 2k\),剩下的 \(n^2-k\) 列就每次选择行交最小的行的颜色选即可。
最终每行的交之和至少是 \(2k+n^2-k=n^2+k\),如果 \(k=n\) 就做完;如果 \(k<n\),则说明所有 \(c_{i}>1\) 的都在 \(i_j\) 里,那么 \(\sum c_{i_j}+(n^2-k)\geq n(n+1)\),所以这种情况每行的交还是能保持在 \(n+1\),也就证完了。
然后直接 dp 即可。
时间复杂度:\(O(n^4)\)。
代码
#include <bits/stdc++.h>
// #define int int64_t
const int kMaxN = 55, kMaxS = kMaxN * (kMaxN + 1), kMod = 998244353;
int n;
int C[kMaxN][kMaxN], f[kMaxS][kMaxN], g[kMaxN][kMaxN][kMaxN];
int qpow(int bs, int64_t idx = kMod - 2) {
int ret = 1;
for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
if (idx & 1)
ret = (int64_t)ret * bs % kMod;
return ret;
}
inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }
void prework(int n = 50) {
C[0][0] = 1;
for (int i = 1; i <= n; ++i) {
C[i][0] = 1;
for (int j = 1; j <= i; ++j) C[i][j] = add(C[i - 1][j], C[i - 1][j - 1]);
}
}
void getg() {
g[0][0][0] = 1;
for (int i = 1; i <= n; ++i) {
for (int j = 0; j <= n; ++j) {
for (int k = 0; k <= n; ++k) {
if (!g[i - 1][j][k]) continue;
for (int c = 0; c <= n - k; ++c)
inc(g[i][std::max(j, c)][k + c], 1ll * g[i - 1][j][k] * C[k + c][c] % kMod);
}
}
}
}
void dickdreamer() {
std::cin >> n;
getg();
f[0][0] = 1;
for (int i = 1; i <= n * n; ++i) {
for (int j = 0; j <= n; ++j) {
if (!f[i - 1][j]) continue;
for (int k = 1; k <= n; ++k)
inc(f[i][std::min(j + k - 1, n)], 1ll * f[i - 1][j] * g[n][k][n] % kMod);
}
}
std::cout << sub(qpow(n, n * n * n), f[n * n][n]) << '\n';
}
int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
prework();
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}
CF2178I. Numbers or Fireworks(计数,dp,猜结论,meet in the middle)
先把距离等于 \(k\) 的点连边后画出来会发现图是个二分图!
因为当 \(k\) 是奇数时,每条边一定连接坐标轴上颜色不同的两点。当 \(k\) 是偶数时,原坐标轴上的黑点和白点之间互相独立,而每种点的内部先转 \(45^{\circ}\) 后再缩小 \(\sqrt 2\) 倍后就能让 \(k\) 除以二,且仍是一堆整点,这么一直做下去直到 \(k\) 是奇数时即可。
再观察式子会发现实际上要求的就是对于所有没选点,在其有连边的选了的点中面随便找一个点连上或者不连,问方案数。
设二分图两边是 \(S_1\) 和 \(S_2\) 且 \(|S_1|\leq |S_2|\),容易发现一定要暴力枚举一边选了的点集,我们枚举 \(S_1\) 选的点集。
然后依次遍历 \(S_2\) 中的点 \(x\),设 \(T\) 是 \(x\) 在钦定 \(S_1\) 选的点集中有连边的点集,\(f_{i,S}\) 表示考虑了 \(S_2\) 的前 \(i\) 个点,\(S\) 中的点已经找到选择的点了的方案数。分讨 \(x\) 是选还是不选:
- \(x\) 不选:\(f_{i,S}\leftarrow f_{i-1,S}\cdot (|T|+1)\)。
- \(x\) 选: 枚举 \(T'\subseteq T,T'\cap S=\varnothing\),\(f_{i,S\cup T'}\leftarrow f_{i-1,S}\)。
直接枚举子集转移是 \(O(4^{|S_1|}n)\) 的,实际上 2 号转移直接对于 \(x\) 邻域的每个点单独转移即可做到 \(O(3^{|S_1|}m)\),\(m\) 是边数。
代码
#include <bits/stdc++.h>
// #define int int64_t
const int kMaxN = 35, kMod = 998244353;
int n, k;
int x[kMaxN], y[kMaxN], col[kMaxN];
bool g[kMaxN][kMaxN];
std::vector<int> id[2];
int qpow(int bs, int64_t idx = kMod - 2) {
int ret = 1;
for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
if (idx & 1)
ret = (int64_t)ret * bs % kMod;
return ret;
}
inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }
void dfs(int u) {
id[col[u]].emplace_back(u);
for (int v = 1; v <= n; ++v) {
if (g[u][v] && col[v] != -1) assert(col[v] == (col[u] ^ 1));
if (g[u][v] && col[v] == -1) {
col[v] = col[u] ^ 1;
dfs(v);
}
}
}
void dickdreamer() {
std::cin >> n >> k;
for (int i = 1; i <= n; ++i) std::cin >> x[i] >> y[i];
id[0].clear(), id[1].clear();
memset(col, -1, sizeof(col));
memset(g, 0, sizeof(g));
for (int i = 1; i <= n; ++i)
for (int j = i + 1; j <= n; ++j)
if ((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]) == k)
g[i][j] = g[j][i] = 1;
for (int i = 1; i <= n; ++i)
if (col[i] == -1)
col[i] = 0, dfs(i);
if (id[0].size() > id[1].size()) std::swap(id[0], id[1]);
int ans = 0;
for (int t = 0; t < (1 << id[0].size()); ++t) {
static int f[2][1 << 15];
std::vector<int> vec;
for (int i = 0; i < id[0].size(); ++i)
if (~t >> i & 1)
vec.emplace_back(id[0][i]);
int o = 0;
std::fill_n(f[o], 1 << vec.size(), 0);
f[o][0] = 1;
for (auto u : id[1]) {
o ^= 1;
std::fill_n(f[o], 1 << vec.size(), 0);
{ // u 不选
int cnt = 0;
for (int i = 0; i < id[0].size(); ++i)
if ((t >> i & 1) && g[id[0][i]][u])
++cnt;
for (int s = 0; s < (1 << vec.size()); ++s) inc(f[o][s], 1ll * (cnt + 1) * f[o ^ 1][s] % kMod);
}
{ // u 选
static int tmp[1 << 15];
std::fill_n(tmp, 1 << vec.size(), 0);
for (int s = 0; s < (1 << vec.size()); ++s) tmp[s] = f[o ^ 1][s];
for (int i = 0; i < vec.size(); ++i) {
if (!g[vec[i]][u]) continue;
for (int s = 0; s < (1 << vec.size()); ++s)
if (~s >> i & 1)
inc(tmp[s | (1 << i)], tmp[s]);
}
for (int s = 0; s < (1 << vec.size()); ++s) inc(f[o][s], tmp[s]);
}
}
for (int s = 0; s < (1 << vec.size()); ++s) inc(ans, f[o][s]);
}
std::cout << sub(ans, 1) << '\n';
}
int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}
P11824. [湖北省选模拟 2025] 团队协作 / team(数据结构,ddp,静态toptree,转置原理)
容易得到一个 \(O(nV)\) 的暴力是对于每个阈值 \(k\),求出 \(F_{u,k}\) 表示包含 \(u\) 且最大值不超过 \(k\) 的独立集数,\(u\) 的答案就是 \(F_{u,V}\times V-\sum_{i=1}^{V-1}{F_{u,i}}\)。
先离散化,问题等价于初始每个点都能选,每次需要支持:
- 删掉一个点(让这个点之后都不能选)。
- 对于每个还活着的点 \(u\),将 \(u\) 的答案加上给定的权值 \(w\) 乘当前局面下 \(u\) 必须选在独立集内的方案数。
这题应该是可以用重链剖分做的,但是这里用静态 top tree 的做法,会更加简单。dp 状态是 \(f_{x,0/1,0/1}\) 表示 \(x\) 这个簇的子树,钦定 \(x\) 的上界点不选/选,下界点不选/选的方案数。
先考虑只有一个 2 操作的情况。每次都枚举 \(u\) 再重置一遍 \(u\) 在 top tree 上的 dp 数组并更新 \(u\) 祖先簇的话时间复杂度是 \(O(n\log n)\),但是无法拓展到有多次 \(2\) 操作的情况。注意到上面那个东西慢在每次都需要枚举叶子,而最终的需要求的答案只有根。所以可以再加一个状态 \(g_{x,0/1,0/1}\) 表示 \((x,0/1,0/1)\) 这个状态对根的答案的贡献系数,答案就是 \(\sum g_{u,0,1}\)。
具体 \(g\) 的转移就以 Compress 点为例,如果有一个转移是 \(f_{x,a,c}\leftarrow f_{ls,a,b}\times f_{rs,b,c}\),我们就让 \(g_{ls,a,b}\leftarrow g_{x,a,c}\times f_{rs,b,c},g_{rs,b,c}\leftarrow g_{x,a,c}\times f_{ls,a,bc}\)。Rake 点同理,从浅到深转移就能单次 \(O(n)\) 做二操作了。而且会发现这个东西很容易拓展到多次二操作,因为 \(g\) 的转移非常像 pushdown 操作,如果我们让 \(g\) 每次下传后清空的话,做一次权值为 \(w\) 的二操作实际上就是让 \(g_{rt,0,0}\leftarrow w,g_{rt,0,1}\leftarrow w\)!所以只要再在删掉点 \(x\) 的时候将 \(x\) 在 top tree 上的所有祖先簇按照顺序 pushdown 一遍,最后 \(g_{x,0,1}\) 就是 \(x\) 的答案,这时再修改 \(x\) 的 dp 值并更新祖先即可。时间复杂度是 \(O(n\log n)\)。
代码
#include <bits/stdc++.h>
// #define int int64_t
const int kMaxN = 3e5 + 5, kMod = 998244353;
enum Type_t { COMPRESS, RAKE };
struct Node {
int ls, rs, fa, u, v;
Type_t type;
int f[2][2], tag[2][2];
Node(int _ls = 0, int _rs = 0, int _fa = 0, int _u = 0, int _v = 0, Type_t _type = COMPRESS) :
ls(_ls), rs(_rs), fa(_fa), u(_u), v(_v), type(_type) { memset(f, 0, sizeof(f)), memset(tag, 0, sizeof(tag)); }
} t[kMaxN * 2];
int n, m, top_cnt, rt;
int p[kMaxN], a[kMaxN], unq[kMaxN], res[kMaxN];
int id[kMaxN], sz[kMaxN], wson[kMaxN];
std::vector<int> G[kMaxN], vec[kMaxN];
int qpow(int bs, int64_t idx = kMod - 2) {
int ret = 1;
for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
if (idx & 1)
ret = (int64_t)ret * bs % kMod;
return ret;
}
inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }
void discrete() {
for (int i = 1; i <= n; ++i) unq[i] = a[i];
std::sort(unq + 1, unq + 1 + n);
m = std::unique(unq + 1, unq + 1 + n) - (unq + 1);
for (int i = 1; i <= n; ++i)
vec[a[i] = std::lower_bound(unq + 1, unq + 1 + m, a[i]) - unq].emplace_back(i);
}
int newnode(int u, int v) {
t[++top_cnt] = {0, 0, 0, u, v, COMPRESS};
return top_cnt;
}
int merge(int x, int y, Type_t type) {
assert(!t[x].fa && !t[y].fa);
int nz = ++top_cnt;
t[x].fa = t[y].fa = nz;
if (type == COMPRESS) {
assert(t[x].v == t[y].u);
t[nz] = {x, y, 0, t[x].u, t[y].v, COMPRESS};
} else {
assert(t[x].u == t[y].u);
t[nz] = {x, y, 0, t[x].u, t[x].v, RAKE};
}
return nz;
}
void dfs1(int u, int fa) {
sz[u] = 1, id[u] = newnode(fa, u);
for (auto v : G[u]) {
if (v == fa) continue;
dfs1(v, u);
sz[u] += sz[v];
if (sz[v] > sz[wson[u]]) wson[u] = v;
}
}
int build(std::vector<int> &id, std::vector<int> &pre, int l, int r, Type_t type) {
assert(l <= r);
if (l == r) return id[l];
int sum = pre[r] - (!l ? 0 : pre[l - 1]);
int L = l - 1, R = r + 1, res = r + 1;
while (L + 1 < R) {
int mid = (L + R) >> 1;
if ((pre[mid] - (!l ? 0 : pre[l - 1])) * 2 >= sum) R = res = mid;
else L = mid;
}
res = std::min<int>(res, r - 1);
return merge(build(id, pre, l, res, type), build(id, pre, res + 1, r, type), type);
}
int build(int u, int fa) {
std::vector<int> cid, cpre;
cid.emplace_back(id[u]), cpre.emplace_back(1);
for (int x = u, lst = fa; wson[x]; lst = x, x = wson[x]) {
int y = wson[x];
std::vector<int> rid, rpre;
rid.emplace_back(id[y]), rpre.emplace_back(1);
for (auto v : G[x]) {
if (v == lst || v == y) continue;
rid.emplace_back(build(v, x)), rpre.emplace_back(sz[v] + rpre.back());
}
cid.emplace_back(build(rid, rpre, 0, rid.size() - 1, RAKE)), cpre.emplace_back(rpre.back() + cpre.back());
}
return build(cid, cpre, 0, cid.size() - 1, COMPRESS);
}
void build() {
dfs1(1, 0), rt = build(1, 0);
}
void pushup(int x) {
if (!t[x].ls && !t[x].rs) return;
int ls = t[x].ls, rs = t[x].rs;
memset(t[x].f, 0, sizeof(t[x].f));
if (t[x].type == COMPRESS) {
for (int i = 0; i < 2; ++i)
for (int j = 0; j < 2; ++j)
for (int k = 0; k < 2; ++k)
t[x].f[i][k] = (t[x].f[i][k] + 1ll * t[ls].f[i][j] * t[rs].f[j][k]) % kMod;
} else {
for (int i = 0; i < 2; ++i)
for (int j = 0; j < 2; ++j)
for (int k = 0; k < 2; ++k)
t[x].f[i][j] = (t[x].f[i][j] + 1ll * t[ls].f[i][j] * t[rs].f[i][k]) % kMod;
}
}
void pushdown(int x) {
if (!t[x].ls && !t[x].rs) return;
int ls = t[x].ls, rs = t[x].rs;
if (t[x].type == COMPRESS) {
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 2; ++j) {
for (int k = 0; k < 2; ++k) {
t[ls].tag[i][j] = (t[ls].tag[i][j] + 1ll * t[x].tag[i][k] * t[rs].f[j][k]) % kMod;
t[rs].tag[j][k] = (t[rs].tag[j][k] + 1ll * t[x].tag[i][k] * t[ls].f[i][j]) % kMod;
}
}
}
} else {
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 2; ++j) {
for (int k = 0; k < 2; ++k) {
t[ls].tag[i][j] = (t[ls].tag[i][j] + 1ll * t[x].tag[i][j] * t[rs].f[i][k]) % kMod;
t[rs].tag[i][k] = (t[rs].tag[i][k] + 1ll * t[x].tag[i][j] * t[ls].f[i][j]) % kMod;
}
}
}
}
memset(t[x].tag, 0, sizeof(t[x].tag));
}
void solve(int w) { inc(t[rt].tag[0][0], w), inc(t[rt].tag[0][1], w); }
void update(int x) {
std::vector<int> anc;
for (int i = t[id[x]].fa; i; i = t[i].fa) anc.emplace_back(i);
for (int i = (int)anc.size() - 1; ~i; --i) pushdown(anc[i]);
res[x] = t[id[x]].tag[0][1];
t[id[x]].f[0][1] = 0;
for (int i = t[id[x]].fa; i; i = t[i].fa) pushup(i);
}
void dickdreamer() {
std::cin >> n;
for (int i = 2; i <= n; ++i) {
std::cin >> p[i];
G[p[i]].emplace_back(i), G[i].emplace_back(p[i]);
}
for (int i = 1; i <= n; ++i) std::cin >> a[i];
discrete(), build();
for (int i = 1; i <= n; ++i) t[id[i]].f[0][0] = t[id[i]].f[0][1] = t[id[i]].f[1][0] = 1;
for (int i = 1; i <= top_cnt; ++i) pushup(i);
solve(unq[m]);
for (int i = m; i; --i) {
for (auto x : vec[i]) update(x);
solve(sub(unq[i - 1], unq[i]));
}
for (int i = 1; i <= n; ++i) std::cout << res[i] << " \n"[i == n];
}
int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}
P14637. [NOIP2025] 树的价值(dp,最优化,长链剖分,dp不好维护可以加状态)
首先可以得到问题等价于给整棵树进行链剖分,设 \(c_i\) 表示 \(i\) 到 \(i\) 所在链顶的点数,\(d_i\) 是 \(i\) 及 \(i\) 所有祖先 \(c\) 的最大值,最大化 \(\sum d_i\)。
我先是想到了设计状态 \(f_{u,i}\) 表示 \(u\) 是 \(u\) 所在链的链头,且钦定 \(d_{p_u}=i\) 时 \(u\) 子树贡献的最大值,这个状态容易通过枚举 \(u\) 所在链是什么来做到 \(O(nm^2)\),代码如下:
void dfs2(int u, int len, int rt, int *s) {
for (int i = 1; i <= m; ++i) s[i] += std::max(i, len);
if (!G[u].size()) {
for (int i = 1; i <= m; ++i) chkmax(f[rt][i], s[i]);
} else {
for (auto v : G[u]) {
for (int i = 1; i <= m; ++i) s[i] += g[u][std::max(i, len)] - f[v][std::max(i, len)];
dfs2(v, len + 1, rt, s);
for (int i = 1; i <= m; ++i) s[i] -= g[u][std::max(i, len)] - f[v][std::max(i, len)];
}
}
for (int i = 1; i <= m; ++i) s[i] -= std::max(i, len);
}
void dfs1(int u) {
static int tmp[kMaxM];
for (auto v : G[u]) dfs1(v);
memset(tmp, 0, sizeof(tmp));
dfs2(u, 1, u, tmp);
for (int i = 1; i <= m; ++i) g[p[u]][i] += f[u][i];
}
观察这个式子会发现状态数虽然是 \(O(nm)\) 的,但是转移需要做 \(nm\) 次对位相加,最后还要 chkmax,这个是根本无法维护的。
又因为这个状态已经达到最简化了,所以考虑加一维,来尽量让它能够维护。
设 \(f_{u,i,j}\) 表示钦定 \(d_{u}=i,c_u=j\) 时 \(u\) 子树贡献的最大值,转移方程如下:
直接维护也可做到 \(O(nm^2)\),这个转移乍一看还是不能优化,但是注意到如果 \(i-j>len_u\)(\(len_u\) 是以 \(u\) 为链顶的长链程度),\(f_{u,i,j}\) 一定等于 \(f_{u,i,i-len_u}\),因为 \(j\) 怎么加都不可能超过 \(i\),所以只需要记录 \(i-j\leq len_u\) 的状态!
进一步地,我们让第三维 \(j\) 变为 \(d_u-c_u\),转移方程式就变为:
由于第二维的 \(i\) 只和 \(i+1\) 有关,所以可以先全局枚举 \(i\),记录 \(g_u=f_{u,i+1,0}\),转移就是:
这个东西直接长剖维护即可,时间复杂度是 \(O(nm)\)。
代码
#include <bits/stdc++.h>
// #define int int64_t
const int kMaxN = 8e3 + 5, kMaxM = 805;
int n, m, d;
int p[kMaxN], len[kMaxN], lson[kMaxN];
int *ptr, pool[kMaxN * 2], *f[kMaxN], g[kMaxN], h[kMaxN], val[kMaxN], tag[kMaxN];
std::vector<int> G[kMaxN];
inline void chkmax(int &x, int y) { x = (x > y ? x : y); }
inline void chkmin(int &x, int y) { x = (x < y ? x : y); }
void dfs1(int u) {
len[u] = 1, lson[u] = 0;
for (auto v : G[u]) {
dfs1(v);
if (len[v] > len[lson[u]]) len[u] = len[v] + 1, lson[u] = v;
}
}
int getreal(int u, int i) { assert(i <= len[u]); return f[u][i] + tag[u]; }
void dfs2(int u) {
tag[u] = 0;
if (u != lson[p[u]]) f[u] = ptr, ptr += len[u] + 1;
int s = 0;
if (lson[u]) {
f[lson[u]] = f[u] + 1;
dfs2(lson[u]);
tag[u] = tag[lson[u]], s += val[lson[u]];
}
for (auto v : G[u]) {
if (v == lson[u]) continue;
dfs2(v);
tag[u] += val[v], s += val[v];
}
f[u][0] = -tag[u];
for (auto v : G[u]) {
if (v == lson[u]) continue;
for (int i = 1; i <= std::min(len[u], len[v] + 1); ++i)
chkmax(f[u][i], s - val[v] + getreal(v, i - 1) - tag[u]);
}
for (auto v : G[u]) chkmax(f[u][0], s - val[v] + g[v] - tag[u]);
tag[u] += d, h[u] = getreal(u, 0), val[u] = getreal(u, std::min(d - 1, len[u]));
}
void dickdreamer() {
std::cin >> n >> m;
for (int i = 1; i <= n; ++i) G[i].clear(), g[i] = h[i] = 0;
for (int i = 2; i <= n; ++i) {
std::cin >> p[i];
G[p[i]].emplace_back(i);
}
dfs1(1);
for (d = m + 10; d; --d) { // g[u] = f[d + 1][0]
memset(pool, 0, sizeof(pool));
ptr = pool;
dfs2(1);
for (int i = 1; i <= n; ++i) g[i] = h[i];
// std::cerr << g[1] << ' ' << g[2] << '\n';
}
std::cout << g[1] << '\n';
}
int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

浙公网安备 33010602011771号