【带权并查集题目汇总】

食物链(POJ 1182):

题意: N N 个动物,编号为 1 N 1-N ,每个动物都是是 A , B , C A,B,C 中的一种,但是我们并不知道它们是哪一种。现在给出 K K 句话, 1   X   Y “1 \ X \ Y” 表示 X X Y Y 是同类, 2   X   Y “2 \ X \ Y” 表示 X X Y Y 。如果当前的话于前面的某些真的话冲突,那么这句话就是假话,问一共有多少句假话。

思路: 已知 A B C A A\rightarrow B\rightarrow C\rightarrow A ,一个环形的状态。因此类似于一个模 3 3 剩余系, d [ x ] = 0 d[x] = 0 表示 x x r o o t root 同类别, d [ x ] = 1 d[x] = 1 表示 x x r o o t root d [ x ] = 2 d[x] = 2 表示 x x r o o t root 吃。因此所有偏移量加减都在模 3 3 剩余系中进行,具体实现可以查看代码。

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
typedef long long ll;
typedef double db;
const db EPS = 1e-9;
using namespace std;
const int N = 5*1e4+100;

int n,m,fa[N],d[N];

int find(int x){
	if(x == fa[x]) return x;
	int root = find(fa[x]);
	d[x] = (d[fa[x]]+d[x])%3;
	return fa[x] = root;
}

int main()
{
	scanf("%d%d",&n,&m);
	int ans = 0;
	rep(i,0,n) fa[i] = i, d[i] = 0;
	rep(i,1,m){
		int op,x,y;
		scanf("%d%d%d",&op,&x,&y);
		if(x > n || y > n){
			ans++;
			continue;
		}
		if(x == y && op == 2){
			ans++;
			continue;
		}
		int fx = find(x), fy = find(y);
		if(fx == fy){
			if(op == 1){
				if((d[x]-d[y]+3)%3!=0) ans++;
			}
			else{
				if((d[x]-d[y]+3)%3!=1) ans++;
			}
		}
		else{
			if(op == 1){
				fa[fx] = fy;
				d[fx] = (d[y]-d[x]+3)%3;
			}
			else{
				fa[fx] = fy;
				d[fx] = (d[y]-d[x]+3+1)%3;	
			}
		}
	}
	printf("%d\n",ans);
	return 0;
}

关押罪犯(NOIP 2010 / CODEVS 1069):

题意: 现在有 N N 名罪犯,编号分别为 1 N 1-N 。有 M M 对罪犯之间存在仇恨关系,有具体的怨气值 c c 。现在需要将 N N 名罪犯安排到两所监狱中,如果有仇恨的两名罪犯在同一监狱,则会发生冲突,事件影响力为其对应的怨气值。问如果分配罪犯,才能使发生的冲突中最大的影响值最小。输出答案即可。 ( N 2 1 0 4 , M 1 0 5 ) (N\leq 2*10^4,M\leq 10^5)

思路: 由于本题只需要求影响力最大的冲突,因此我们可以贪心地来解决这个问题,先按照怨气值从大到小将 M M 对罪犯进行排序,然后建立一个带权并查集, d [ x ] = x r o o t d[x] = x\rightarrow root 表示 x x r o o t root 是否在同一个监狱。 d [ x ] = 0 d[x] = 0 表示 x x r o o t root 在同一监狱, d [ x ] = 1 d[x] = 1 表示 x x r o o t root 在不同监狱,因此所有的偏移量计算都在模 2 2 剩余系中进行,也可以转化为异或计算。

所以对于每一对罪犯,查看他们所属并查集,如果不在同一并查集则将两者合并,并将其偏移量设为 1 1 。如果在同一并查集,则查看二者的偏移量,若偏移量为 0 0 ,则冲突不可避免,输出答案即可。

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
typedef long long ll;
typedef double db;
const db EPS = 1e-9;
using namespace std;
const int N = 2*1e4+1000;
const int M = 1e5+100;

int n,m,fa[N],d[N];
struct Node{
	int a,b,c;
	bool operator < (Node x) const {
		return c > x.c;
	}
}t[M];

int find(int x){
	if(x == fa[x]) return x;
	int root = find(fa[x]);
	d[x] = (d[x]+d[fa[x]]+2)%2;
	return fa[x] = root;
}

int main()
{
	int ans = 0;
	scanf("%d%d",&n,&m);
	rep(i,1,m) scanf("%d%d%d",&t[i].a,&t[i].b,&t[i].c);
	sort(t+1,t+1+m);
	rep(i,0,n) fa[i] = i, d[i] = 0;
	rep(i,1,m){
		int x = t[i].a, y = t[i].b;
		int fx = find(x), fy = find(y);
		if(fx != fy){
			fa[fx] = fy;
			d[fx] = (1+d[y]-d[x]+2)%2;
		}
		else{
			int yt = (d[x]-d[y]+2)%2;
			if(!yt){
				ans = t[i].c;
				break;
			} 
		}
	}
	printf("%d\n",ans);
	return 0;
}

Rochambeau(POJ 2912):

题意: N N 个人在玩剪刀石头布的游戏,其中有一个人是裁判,其余人随机被分到了三个阵营,即剪刀、石头、布。现在有 M M 轮游戏, x &lt;   , &gt;   , = y x &lt;\ ,&gt;\ ,= y 表示 x x y y 之间的关系,其中裁判可以自由变换阵营。问是否可以根据这 M M 轮游戏,判断出谁是裁判,输出裁判是谁以及最早在哪一轮可以找到裁判。如果无法判断,则输出 C a n   n o t   d e t e r m i n e Can\ not\ determine ,如果游戏的情况不符合题意,则输出 I m p o s s i b l e Impossible ( N 500 , M 2000 ) (N\leq 500,M\leq 2000)

思路: 一开始做这题的时候,的确有些懵,只能想到如何发现有人变换了阵营,但是不知道如何找到这个人,并且确定这种情况是唯一的。

所以我们可以发现直接考虑整个问题会非常困难,再加上此题数据范围很小,直接做的话复杂度肯定很少,太对不起这个数据范围了。因此我们考虑 N 2 N^2 做法,即枚举每个人为裁判。枚举 x x 为裁判时,如果 x x 恰好为裁判,则并查集合并时不会发生矛盾。我们统计有多少个人为裁判时,并查集合并会发生矛盾,记人数为 c n t cnt ,并且统计每个人为裁判时,发生矛盾的轮数 r i ri

现在我们来考虑输出答案的所有情况。
1. 1. 如果 c n t = n 1 cnt = n-1 ,则可以唯一确定裁判,并且最早发现该裁判的轮数为 m a x ( r i ) max(ri)
2. 2. 如果 c n t = n cnt = n ,则输出 i m p o s s i b l e impossible
3. 3. 其余情况则为 C a n   n o t   d e t e r m i n e Can\ not\ determine

至于带权并查集的合并类似于食物链问题,是个模 3 3 剩余系中的加减问题。

ps:读输入的时候1<3,如果用字符串读,不要只读一个数字… 推荐读取方式如下。

scanf("%d%c%d") 

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
typedef long long ll;
typedef double db;
const db EPS = 1e-9;
using namespace std;
const int N = 1000;
const int M  = 2000+100;
int n,m,fa[N],d[N],vis[N],error[N],cnt;

int find(int x){
	if(x == fa[x]) return x;
	int root = find(fa[x]);
	d[x] = (d[x]+d[fa[x]]+3)%3;
	return fa[x] = root;
}

struct Query{
	int x,y,cc;
}q[M];

int main()
{
	while(~scanf("%d%d",&n,&m))
	{
		memset(vis,0,sizeof vis);
		memset(error,0,sizeof error);
		cnt = 0;
		rep(i,1,m){
			int x,y; char cc;
			scanf("%d%c%d",&x,&cc,&y);
			q[i].x = x, q[i].y = y;
			if(cc == '=') q[i].cc = 0;
			else if(cc == '<') q[i].cc = 1;
			else q[i].cc = 2;
		}
		rep(i,0,n-1){
			rep(k,0,n) fa[k] = k, d[k] = 0;
			rep(j,1,m){
				int x = q[j].x, y = q[j].y, cc = q[j].cc;
				if(x == i || y == i) continue;
				int fx = find(x), fy = find(y);
				if(fx != fy){
					fa[fx] = fy;
					d[fx] = (d[y]-d[x]+cc+3)%3;
				}
				else{
					int ttp = (d[x]-d[y]+3)%3;
					if(ttp != cc){
						cnt++;		// 产生矛盾个数
						error[i] = j;
						vis[i] = 1;
						break;
					} 
				}
			}
		}
		// impossible
		// not determine
		// determine
		if(cnt == (n-1)) { //determine
			int ans = 0, hm;
			rep(i,0,n-1){
				ans = max(ans,error[i]);
				if(!vis[i]) hm = i;
			} 
			printf("Player %d can be determined to be the judge after %d lines\n",hm,ans);
		}
		else if(cnt == n){ // impossible
			printf("Impossible\n");
		}	
		else{  // not determine
			printf("Can not determine\n");
		}
	}
	return 0;
}

True Liars(POJ 1417):

题意: 给出 p 1 p1 个天使, p 2 p2 个魔鬼,一共有 n n 个问题。每个问题的格式为 x   y   ( y e s   o r   n o ) x\ y\ (yes\ or\ no) 表示问 x x y y 是否为天使,如果 x x 为天使,则会说真话,如果 x x 为魔鬼,则会说假话。问根据这 n n 个问题,是否可以确定哪些人为天使,如果可以确定,按编号大小输出天使编号。 ( n 1000 ,    p 1 , p 2 300 ) (n\leq 1000,\ \ p1,p2\leq 300)

思路: 遇到这样的 y e s yes o r or n o no 问题,显然我们需要对于可能出现的情况进行模拟。
1. 1. 假如 x x 为天使, y y 为天使,则回答 y e s yes
2. 2. 假如 x x 为天使, y y 为魔鬼,则回答 n o no
3. 3. 假如 x x 为魔鬼, y y 为天使,则回答 n o no
4. 4. 假如 x x 为魔鬼, y y 为魔鬼,则回答 y e s yes

可以发现,如果回答是 y e s yes ,则 x x y y 属于同一类。如果回答 n o no ,则 x x y y 类别相反。因此一个模 2 2 剩余系的带权并查集合并就可以维护所有人之间的关系。

因此不难发现,处理完 n n 个问题之后,我们会拥有若干个并查集,每个并查集中都会分为两批人,两批人类别不同。

因此问题变成了,从这若干个并查集中随机选一部分,使得最后选中的人数恰好为 p 1 p1 ,并且这种选择方式唯一,则我们可以确定哪些人为天使。因此本题就变成了一个类似背包的问题, d p [ i ] [ j ] dp[i][j] 表示前 i i 个并查集选取人数为 j j 一共有多少种选择方案, p r e [ i ] [ j ] = x pre[i][j]=x 表示 d p [ i ] [ j ] dp[i][j] d p [ i 1 ] [ x ] dp[i-1][x] 更新而来。到此,本题即可顺利解决。

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <map>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
typedef long long ll;
typedef double db;
const db EPS = 1e-9;
using namespace std;
const int N = 700;

int n,p1,p2,fa[N],d[N],base[N][2],tot,dp[N][600],pre[N][600],vis[N];
map<int,int> mp;

int find(int x)
{
	if(x == fa[x]) return x;
	int root = find(fa[x]);
	d[x] = (d[x]+d[fa[x]]+2)%2;
	return fa[x] = root;
}

void solve()
{
	memset(base,0,sizeof base);
	memset(vis,0,sizeof vis);
	mp.clear();
	tot = 0;
	rep(i,1,p1+p2){
		fa[i] = find(i);
		if(mp.find(fa[i]) == mp.end()) mp[fa[i]] = ++tot;
		int pos = mp[fa[i]];
		base[pos][d[i]]++;	//0: 相同	1:不同
	}
	memset(dp,0,sizeof dp);
	memset(pre,0,sizeof pre);
	dp[1][base[1][1]]++;
	dp[1][base[1][0]]++;
	rep(i,2,tot){
		int minn = min(base[i][0],base[i][1]);
		rep(j,minn,p1){
			if(j >= base[i][0] && dp[i-1][j-base[i][0]] != 0)
				dp[i][j] += dp[i-1][j-base[i][0]], pre[i][j] = j-base[i][0];
			if(j >= base[i][1] && dp[i-1][j-base[i][1]] != 0)
				dp[i][j] += dp[i-1][j-base[i][1]], pre[i][j] = j-base[i][1];
		}
	}
	if(dp[tot][p1] != 1) printf("no\n");
	else{
		int xx = tot, yy = p1;
		while(xx > 1){
			if(pre[xx][yy] == yy-base[xx][0]) vis[xx] = 0, yy -= base[xx][0];
			else if(pre[xx][yy] == yy-base[xx][1]) vis[xx] = 1, yy -= base[xx][1];
			xx--;
		}
		if(xx == 1){
			if(base[1][0] == yy) vis[1] = 0;
			else vis[1] = 1;
		}
		rep(i,1,p1+p2){
			int pos = mp[fa[i]];
			int dd = d[i];
			if(vis[pos] == 1 && dd == 1) printf("%d\n",i);
			else if(vis[pos] == 0 && dd == 0) printf("%d\n",i);
		}
		printf("end\n");
	}
}

int main()
{
	while(~scanf("%d%d%d",&n,&p1,&p2))
	{
		if((n+p1+p2) == 0) break; 
		rep(i,0,p1+p2) fa[i] = i, d[i] = 0;
		rep(i,1,n){
			int x,y; char op[10];
			scanf("%d%d",&x,&y);
			scanf("%s",op);
			int fx = find(x), fy = find(y);
			// LOG2("x",x,"y",y);
			// LOG2("fx",fx,"fy",fy);
			if(fx != fy){
				fa[fx] = fy;
				if(op[0] == 'n') d[fx] = (2+1+d[y]-d[x])%2;
				else d[fx] = (d[y]-d[x]+2)%2;
			}
		}
		solve();
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41552508/article/details/88736243
今日推荐