1 条题解
-
0
自动搬运
来自洛谷,原作者为

dbxxx
多刷题,少整那些没用的搬运于
2025-08-24 22:35:16,当前版本为作者最后更新于2023-07-18 15:08:14,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
题目只考察连通性,不考察图更具体的结构,所以可以用 个并查集维护。
两个点 和 在图 上连通,当且仅当图 上 和 的并查集祖先相同。
因此,两个点 和 在 张图上都连通,等价于对于任意 , 和 在第 张图的并查集祖先相同。
所以考虑对每个点 维护一个长度为 的字符串 , 表示第 张图上 的并查集祖先。
于是 和 在 张图上连通等价于 。
我们动态地维护这 个字符串的哈希值,对于哈希值相同的 个字符串,可以给答案贡献 。开桶记录即可。
因为桶的下标是字符串哈希值域量级,桶用 unordered_map(如果会哈希表用哈希表也行)。
现在考虑动态维护 个字符串哈希值的复杂度。很明显,每次我们更改的字符串不能太多。
对于一次在第 张图连接 和 的操作,我们会修改 的第 个字符,其中 是所有这次连边中在并查集上被更改祖先的点。我们要控制这个点数的量级。
所以不路径压缩,考虑按并查集树的大小启发式合并。这样以来,一个点在第 张图中,被更改祖先的次数不会超过 次(因为被更改一次祖先,它所在的并查集树的大小就会翻倍),每个字符串被修改的数量不超过 ,所有字符串被修改的总次数为 量级。
时间复杂度 ,常数略大。
另外这里所有字符串长度都是 ,所以可以不用写普通的字符串哈希,直接给每个位置随机一个权值 ,记 为字符串 的第 个字符,我们令 的哈希值为 即可。这样代码会更好写,而且规避了可能的卡哈希问题(本题好像把基数为 131 的自然溢出哈希卡了)。
typedef unsigned long long ull; const int D = 205; const int N = (int)5e3 + 5; int rt[D][N]; std :: vector <int> t[D][N]; // t[k][u] 表示第 k 层图中以 u 为根的并查集树 ull ha[N], val[D]; int ans; std :: unordered_map <ull, int> cnt; inline void add(ull ha) { int x = cnt[ha]; ans += 2 * x + 1; ++cnt[ha]; } inline void del(ull ha) { int x = cnt[ha]; ans -= 2 * x - 1; if (--cnt[ha] == 0) cnt.erase(ha); // 本题一个哈希值消失就很难再现,直接 erase 掉能显著减小常数。不 erase 也没什么问题。 } int main() { int d = read(), n = read(), m = read(); std :: mt19937_64 rng(std :: random_device{}()); for (int i = 1; i <= d; ++i) val[i] = rng(); for (int i = 1; i <= d; ++i) { for (int u = 1; u <= n; ++u) { rt[i][u] = u; ha[u] += u * val[i]; t[i][u].push_back(u); } } for (int u = 1; u <= n; ++u) add(ha[u]); while (m--) { int u = read(), v = read(), k = read(); u = rt[k][u]; v = rt[k][v]; if (u == v) { printf("%d\n", ans); continue; } if (t[k][v].size() > t[k][u].size()) std :: swap(u, v); for (int x : t[k][v]) { t[k][u].push_back(x); rt[k][x] = u; del(ha[x]); ha[x] += (u - v) * val[k]; add(ha[x]); } t[k][v].clear(); printf("%d\n", ans); } return 0; }如果您觉得我的题解写的不错,解决了您的疑惑的话,请给我题解点个赞,谢谢!
- 1
信息
- ID
- 7377
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者