POJ 1417 并查集加dp加记录路径

转载自: by---cxlove

题源: POJ

题目:给出p1+p2个人,其中p1个是好人,p2个是坏人。然后有一些关系 ,a说b是好人(坏人).其中没有矛盾的,判断是否有唯一解判断哪些人是好人,哪些人是坏人。

好人总说真话,坏人总说假话。不需要判断矛盾。唯一解



其中好人说真话,坏人说假话这点很重要。

那么如果一个人说另一个人是好人,那么如果这个人是好人,说明 对方确实是好人,如果这个是坏人,说明这句话是假的,对方也是坏人。

如果一个人说另一个人是坏人,那么如果这个人是好人,说明对方是坏人,如果这个是坏人,说明 对方是好人。

也就是如果条件是yes说明这两个是相同集合的,否则是两个不同的集合。

用r[i]表示i结点与根结点的关系,0为相同集合,1为不同集合。这是一个经典的并查集问题。

这样处理之后,还需要判断是否唯一

我们通过并查集,可以将所有人分为若干个集合,其中对于每一个集合,又分为两个集合(好人和坏人,但是不知道哪些是好人,哪些是坏人,我们只有相对关系)

接下来就是从所有大集合中的两个小集合取一个,组成好人集合,判断是否唯一。

背包问题,dp[i][j]表示前i个大集合,好人为j个的方案有多少种,或者dp[i][j]表示当前好人i个,坏人j个的情况有多少种

如果dp[cnt][p1]!=1说明方案不唯一,或者无解。

如果为1题目还需要输出方案,这点比较纠结。用后一种DP的时候WA了好多次,而这题又卡内存,不能开三维数组,其实可以两次DP解决。

后来采用前者DP,不断从dp[cnt][p1]往前递推,递推的结果也必须是某个前趋状态的dp值为1.


转换方程参见: 点击打开链接
#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define INF 2<<30-1
#define N 605
using namespace std;

typedef long long ll;
int n,p,q;
int pre[N],rating[N];//记录根节点,记录和根节点的关系 
bool vis[N];
int dp[N][N/2];//dp数组 
int cnt;
int a[N][2];//集合数,a[][0]和[1]将集合分为两部分 
vector<int> b[N][2];//记录路径数组 //二级轮换数组 

void build()//建立并查集结构 //附初始化 
{
	for(int i=0;i<=p+q;i++)
	{
		pre[i]=i;
		rating[i]=0;
	}
	memset(vis,false,sizeof(vis));
	cnt=1;
	memset(a,0,sizeof(a));
	for(int i=0;i<N;i++)
	{
		b[i][0].clear();
		b[i][1].clear();
	}
}

int find(int x)//查找函数 
{
	if(x!=pre[x])
	{
		int temp=pre[x];//记住设置中间变量,防WA; 
		pre[x]=find(pre[x]);
		rating[x]=rating[x]^rating[temp];//路径压缩 
	}
	return pre[x];
}

int main()
{
	while(scanf("%d%d%d",&n,&p,&q)!=EOF)
	{
		if(n==0&&p==0&&q==0)
		{
			break;
		}
		
		build();
		int x,y;
		char str[5];
		while(n--)
		{
			scanf("%d%d%s",&x,&y,&str);
			int k;
			int rootx=find(x);
			int rooty=find(y);
			if(str[0]=='n')//方便异或判断 
				 k=1;
			else k=0;
			if(rootx!=rooty)//join函数 
			{
				pre[rootx]=rooty;
				rating[rootx]=rating[x]^rating[y]^k;//从画图可以得知的关系转换,证明类似向量偏移 ?
			}//真牛皮 
		}
		
		for(int i=1;i<=p+q;i++)
		{
			if(!vis[i])
			{
				int f=find(i);
				for(int j=i;j<=p+q;j++) 
				{
					if(find(j)==f)//如果找到了该集合
					{
						vis[j]=true;
						b[cnt][rating[j]].push_back(j);//把同集合,和根节点关系相同的代码加入b中,存储路径使用 
						a[cnt][rating[j]]++;//统计总的各集合0数和1数 
					}
				}
				cnt++; 
			}
		}
		
		memset(dp,0,sizeof(dp));
		dp[0][0]=1;
		for(int i=1;i<cnt;i++)//用好人数启动dp 
		{
			for(int j=p;j>=0;j--)
			{
				if(j-a[i][0]>=0)
				{
					dp[i][j]+=dp[i-1][j-a[i][0]];
				}
				if(j-a[i][1]>=0)
				{
					dp[i][j]+=dp[i-1][j-a[i][1]];
				}
			}
		}
		
		if(dp[cnt-1][p]!=1)//如果不止一条答案 
		{
			printf("no\n");
		}
		else 
		{
			vector<int> ans;
			ans.clear();
			for(int i=cnt-1;i>=1;i--)
			{
				if(p-a[i][0]>=0&&q-a[i][1]>=0&&dp[i-1][p-a[i][0]]==1)//从后向前递推路径 
				{
					for(int j=0;j<b[i][0].size();j++)
					{
						ans.push_back(b[i][0][j]);
					}
					p-=a[i][0];
					q-=a[i][1];
				}
				else if(p-a[i][1]>=0&&q-a[i][0]>=0&&dp[i-1][p-a[i][1]]==1)//两种可能所以 
				{
					for(int j=0;j<b[i][1].size();j++)
					{
						ans.push_back(b[i][1][j]);
					}
					p-=a[i][1];
					q-=a[i][0];
				}
			}
			sort(ans.begin(),ans.end());
			for(int i=0;i<ans.size();i++)
			{
				printf("%d\n",ans[i]);
			}
			printf("end\n");
		}
	}
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值