并查集好题 P6279 Favorite Colors G

这是一道极为有趣的题目,尤其是当我对着一个假的方法写了好久的时候。

加上这个题我感兴趣的正解写的不太好,搞得我没太懂,我想着写一写这个题的扩展并查集做法。

题意

\(N\) 头牛,每个有喜欢的颜色,但是不告诉你这个颜色是什么。

我们有 \(M\) 对奶牛,\((a,b)\) 表示 \(b\) 仰慕 \(a\)

奶牛 b 仰慕奶牛 a。有可能 a=b,此时一头奶牛仰慕她自己。对于任意颜色 c,如果奶牛 x 和 y 都仰慕一头喜欢颜色 c 的奶牛,那么 x 和 y 喜欢的颜色相同

都是 \(10^5\) 级别的。

怎么做

首先我想到了一个极其简单的,却显然不对的做法。

使用一下并查集小姐姐。

我们反向建边,枚举每一个点,把它能到达的放到一块,因为我们知道他们有一样的仰慕对象。

最后我们从 1 开始枚举到 n,只要这个联通快没有标记就打上。

最后求出联通块的统计肯定是正确的,但是求联通快本身是有问题的。

结果自然是不对的。

因为我们这些点本身是拥有喜欢颜色的,两个喜欢同样颜色的可以作为载体让一些本来八杆子打不着的点一样。

就像样例中,3,7 喜欢颜色是一样的,所以才反映出了 1跟 4,5 所喜欢的一样。

如果不明白说明你没有读对题!!!

这也是我做法假的原因之一。

两个牛仰慕的牛并不一定是相同一头牛,不同的牛也可以,前提是这被仰慕的两头牛所喜欢的颜色一样。

不信读题去。

究其根本是什么??? 我们并没有利用并查集真正的去表示出喜欢这个抽象意义的边,从而没有真正表示出它了连通性。

所以我们使用一个扩展域并查集,表示这个点所真正喜欢的颜色。

练边我们使用将仰慕者合并上被仰慕者喜欢的颜色这个点。

这样我们不禁解决了这个问题,而且还顺便解决了仰慕自己的问题。

但是明显这个合并绝对不能像正常的合并走了,因为这两个点在一个联通快内的信息是隐含的,我们如果知道了两个点所喜欢的颜色是一样的,我们自然而然需要把它上边的代表真正喜欢的点连起来,

所以我们每一次将小的做父亲,酱紫我们合并时最后判断两个点是不是真的表示点,如果是,我们就把两个代表真正喜欢颜色的点再连起来。

非常难绷,这个还是写启发式合并吧...

代码

点击查看代码
#include <bits/stdc++.h>
#define getchar getchar_unlocked
using namespace std;
const int MN=1e6+116;
inline int read(){
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9')
        x=x*10+ch-'0',ch=getchar();
    return x*f;
}
int father[MN], n, m;
int find(int x){
	if(father[x]!=x) father[x]=find(father[x]);
	return father[x];
}
void merge(int x, int y){
	x=find(x); y=find(y);
	if(x==y) return;
	if(x>y) swap(x,y);
	father[y]=x;
	if(x<=n&&y<=n) merge(x+n,y+n);
	return;
}
int val[MN];
int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	n=read(); m=read();
	for(int i=1; i<=n*2+1; ++i) father[i]=i;
	for(int i=1; i<=m; ++i){
		int u, v; u=read(), v=read();
		merge(u+n,v);
	}
	int cnt=0;
	for(int i=1; i<=n; ++i){
		int u=find(i);
		if(!val[u]){
			val[u]=++cnt;
		}
		cout<<val[u]<<'\n';
	}
	return 0;
}
posted @ 2025-08-23 19:26  BaiBaiShaFeng  阅读(11)  评论(0)    收藏  举报
Sakana Widget右下角定位