1 条题解

  • 0
    @ 2025-8-24 22:35:16

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar dbxxx
    多刷题,少整那些没用的

    搬运于2025-08-24 22:35:16,当前版本为作者最后更新于2023-07-18 15:08:14,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    欢迎您到我的博客查看本文,谢谢!

    题目只考察连通性,不考察图更具体的结构,所以可以用 dd 个并查集维护。

    两个点 uuvv 在图 ii 上连通,当且仅当图 iiuuvv 的并查集祖先相同。

    因此,两个点 uuvvdd 张图上都连通,等价于对于任意 iiuuvv 在第 ii 张图的并查集祖先相同。

    所以考虑对每个点 uu 维护一个长度为 dd 的字符串 sus_usu(i)s_u(i) 表示第 ii 张图上 uu 的并查集祖先。

    于是 uuvvdd 张图上连通等价于 su=svs_u = s_v

    我们动态地维护这 nn 个字符串的哈希值,对于哈希值相同的 kk 个字符串,可以给答案贡献 k2k^2。开桶记录即可。

    因为桶的下标是字符串哈希值域量级,桶用 unordered_map(如果会哈希表用哈希表也行)。

    现在考虑动态维护 nn 个字符串哈希值的复杂度。很明显,每次我们更改的字符串不能太多。

    对于一次在第 kk 张图连接 aabb 的操作,我们会修改 sus_u 的第 kk 个字符,其中 uu 是所有这次连边中在并查集上被更改祖先的点。我们要控制这个点数的量级。

    所以不路径压缩,考虑按并查集树的大小启发式合并。这样以来,一个点在第 dd 张图中,被更改祖先的次数不会超过 logn\log n 次(因为被更改一次祖先,它所在的并查集树的大小就会翻倍),每个字符串被修改的数量不超过 dlognd\log n,所有字符串被修改的总次数为 dnlogndn\log n 量级。

    时间复杂度 Θ(dnlogn)\Theta(dn\log n),常数略大。

    另外这里所有字符串长度都是 dd,所以可以不用写普通的字符串哈希,直接给每个位置随机一个权值 w(i)w(i),记 S(i)S(i) 为字符串 SS 的第 ii 个字符,我们令 SS 的哈希值为 w(i)×S(i)\sum w(i) \times S(i) 即可。这样代码会更好写,而且规避了可能的卡哈希问题(本题好像把基数为 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
    上传者