POJ - 1417 True Liars POJ - 141 拓展并查集,01背包问题

题目链接

POJ-1417

题意

岛上有说真话的好人和说假话的坏人,给你这两种人的人数。再给出q次问答结果,问答的格式是向a询问b是否是好人,回答是yes或者no。问是否可以分辨出全部好人,是的话打印清单

思路

网上的题解大多都是用带权并查集,统计当前节点和父节点是否同类。我自己第一次写用的是拓展并查集,也就是这篇题解,后来发现带权并查集似乎更好理解一些,如果想看带权并查集请戳这里

这里用的是拓展并查集,开两倍空间,1-n(总人数)为每个人自己,n—2n为和自己相反属性的“自己”。分析一下询问,当回答yes时,a和b一定同为好人/坏人,a+n和b+n同理。那么unite(a,b),unite(a+n,b+n)。如果回答no,ab一定分别是好人和坏人,那么就unite(a,b+n),unite(b,a+n)。第一步处理就完成了

第一步完成后,我们拥有了并查集管理的多个独立块,在每一大块中,都可以根据1-n和n-2n分出两个小块,这两个小块中元素属性不同。需要注意,因为我们用的拓展并查集,这些大块中其实有一半是重复的。比如样例三中,最终分成四大块:1-2-10, 3-8-9, 4-5-6-14, 7-11-12-13。第1和第2块以及第3和第4块统计的是一样的信息(比如第一块信息是1号和2号属性相同,他们和3号属性不同,3-8-9也是一样的)。

在去重后,我们得到了反应属性关系的若干集合,每个集合再划分为两个子集。如果我们能推出谁是好人,那么好人的方案一定是唯一的。具体操作是我们从每个集合中选取一个集合作为好人,最终好人数量为给定的好人人数,且方案唯一,这就用到01背包了。

代码

#include<iostream>
#include<vector>
#include<cstring>  
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define endl "\n"
using namespace std;
	typedef long long ll;
	const int maxn=2050;
	const int inf=0x3f3f3f3f;
	int fa[maxn];//父节点 
	int q,n,m;
	int dp[1050][1050];//dp数组 
	bool ans[1050];//打印答案 
	bool b[1050];//集合去重 
	vector<int>v[maxn]; //暂时记录不同集合包含的点 
	vector<int>v1[maxn];//v1v2存放每个集合的子集 
	vector<int>v2[maxn];//
	void init(){
    
    
		for(int i=0;i<maxn;i++){
    
    
			fa[i]=i;
		}
		memset(dp,0,sizeof(dp));
		memset(b,0,sizeof(b));
		memset(ans,0,sizeof(ans));
		for(int i=0;i<maxn;i++){
    
    
			v[i].clear();v1[i].clear();v2[i].clear();
		}
	}
	int find(int x){
    
    
		if(fa[x]==x)
			return x;
		else
			return fa[x]=find(fa[x]);
	}
	void unite(int x,int y){
    
    
		int xr=find(x),yr=find(y);
		if(xr==yr){
    
    
			return ;
		}
		fa[xr]=yr;
		
	}
	int main(){
    
    
		IOS
		while(cin>>q>>n>>m){
    
    
			if(!q&&!n&&!m)
				break;
			init();
			int sm=n+m;
			while(q--){
    
    
				int a,b;
				string s;
				cin>>a>>b>>s;
				if(s=="yes"){
    
    
					unite(a,b);
					unite(a+sm,b+sm);
				}
				else{
    
    
					unite(a,b+sm);
					unite(a+sm,b);
				}
			}
			if(n==m){
    
    
				cout<<"no"<<endl;
				continue;
			}
			
			for(int i=1;i<=2*sm;i++){
    
    
				int x=find(i);
				v[x].push_back(i);
			}
			int t=1;
			for(int i=1;i<=2*sm;i++){
    
    
				bool bl=0;
				int le=v[i].size();
				for(int j=0;j<le;j++){
    
    
					int tmp=v[i][j];
					if(tmp<=sm){
    
    
						if(b[tmp])
							continue;
						v1[t].push_back(tmp);
						b[tmp]=1;
						bl=1;
					}
					else{
    
    
						if(b[tmp-sm])
							continue;
						v2[t].push_back(tmp-sm);
						b[tmp-sm]=1;
						bl=1;
					}
				}
				if(bl)
					t++;
			}
			
			//dp部分 
			dp[0][0]=1;
			for(int i=1;i<t;i++){
    
    
				int s1=v1[i].size(),s2=v2[i].size();
				for(int j=n;j>=s1;j--)
					dp[i][j]+=dp[i-1][j-s1];
				for(int j=n;j>=s2;j--)
					dp[i][j]+=dp[i-1][j-s2];
			}
			if(dp[t-1][n]!=1)
				cout<<"no"<<endl;
			else{
    
    
				int tmp=n;
				for(int i=t-1;i>=1;i--){
    
    
					int s1=v1[i].size(),s2=v2[i].size();
					if(dp[i-1][tmp-s1]==1){
    
    
						for(int j=0;j<s1;j++)
							ans[v1[i][j]]=1;
						tmp-=s1;
					}
					else{
    
    
						for(int j=0;j<s2;j++)
							ans[v2[i][j]]=1;
						tmp-=s2;
					}
				}
				for(int i=1;i<=sm;i++){
    
    
					if(!ans[i])
						continue;
					cout<<i<<endl;
				}
				cout<<"end"<<endl;
			}
			
		}
		
		return 0;
	} 
	

猜你喜欢

转载自blog.csdn.net/TheSunspot/article/details/107915448