POJ1417


#include<iostream>
#include<queue>
#include<cstring>
#include<vector>
#include<string>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<stack>
typedef long long LL;
const int maxsize = 800;
using namespace std;

//如果已知一个人是天使(恶魔),那么天使一定说这个人是天使(恶魔),恶魔一定说这个人是恶魔(天使)
//列出所有情况不难发现不论是天使还是恶魔回答yes的时候他们都与问题中的人是同一种属性,回答no的时候都是和他是相反的属性
//于是可以利用并查集将这两个点合并起来,所以合并骑来的集合就有两种类型的元素.统计这两种元素的数量,看看是否有凑出p,q的可能

//但是和传统01背包不同的是,最后组成p必须用掉所有集合中其中的一项,传统的01背包一个物品可以选择放入和不放入
//所以如果是二维的dp那是没有没问题的,对于要使用一位的dp的时候,这次的转移只能从上一个物品新生成的状态开始转移,如果是从上上个开始
//转移就会漏掉一个物品
//所以考虑到实现的简单和空间复杂度,使用滚动数组的形式

int Pre[maxsize];
int Rank[maxsize];
int D[maxsize];//0表示不同,1表示相同
int B[maxsize][2];//[0] 存储和这个节点同类的节点的数量, [1]存储和该节点类型相反的节点数量
bool vis[maxsize];//对于vis为真且Pre[i]==i的节点是根节点,他的B是有效B
int P[maxsize][3];
int Pcnt = 0;
void init(int n)
{
	for (int i = 0; i <= n; i++)
	{
		Pre[i] = i;
		Rank[i] = 0;
		B[i][0] = 1;
		B[i][1] = 0;
		D[i] = 0;//自己和自己相同
	}
}
vector<int> Ans;

int find(int x, int &val)
{
	int root = x;
	val = 0;
	while (root != Pre[root])
	{
		val = (val + D[root]) % 2;
		root = Pre[root];
	}

	//已知上一个结点和根的关系,知道这一个结点和上一个结点的关系,推导出这一个节点和根的关系
	int p = 0;
	int t = val;
	while (x != Pre[x])//路径压缩
	{
		t = (t + p) % 2;//这个节点和根的关系
		p = D[x];//这个节点和他的上一个结点的关系
		D[x] = t;//应为要将他直接指向根所以,赋值为她和根的关系
		int tt = x;
		x = Pre[x];//指针移到要处理的下一个节点
		Pre[tt] = root;//将他指向根
	}
	return root;

}



void unit(int root1, int root2, int val)
{
	if (root1 != root2)
	{
		if (Rank[root1]>Rank[root2])
		{
			Pre[root2] = root1;
			D[root2] = val;
			if (val == 0)
			{
				B[root1][0] += B[root2][0];
				B[root1][1] += B[root2][1];
			}
			else
			{
				B[root1][0] += B[root2][1];
				B[root1][1] += B[root2][0];
			}
		}
		else
		{
			Pre[root1] = root2;
			D[root1] = val;
			if (val == 0)
			{
				B[root2][0] += B[root1][0];
				B[root2][1] += B[root1][1];
			}
			else
			{
				B[root2][0] += B[root1][1];
				B[root2][1] += B[root1][0];
			}
			if (Rank[root1] == Rank[root2])
			{
				Rank[root2]++;
			}
		}
	}
}
int dp[maxsize];//统计数量
int dpans[610][307][3];//他是由哪一个转移过来的,由哪一个物品转移过来的
int dpcnt[maxsize];//标记这一个状态是由那个物品生成的
int main()
{
	//freopen("finput.txt", "r", stdin);
	int n, p, q;
	bool flag;
	while (scanf("%d%d%d", &n, &p, &q) == 3)
	{
		flag = true;
		if ((n == 0 && p == 0 )&& q == 0)
			break;
		if (p == q)//这是不可能确定那些人是天使
			flag = false;
		init(707);
		memset(vis, 0, sizeof(vis));
		for (int i = 1; i <= p + q; i++)
		{
			vis[i] = 1;
		}
		while (n--)
		{
			int x, y;
			char cs[5];
			scanf("%d%d%s", &x, &y, cs);
			if (!flag) continue;
			int valx, valy;
			int root1 = find(x, valx);
			int root2 = find(y, valy);
			int val = 0;
			if (cs[0] == 'n') val = 1;
			if (root1 != root2)//两个集合尚未建立关系
			{
				unit(root1, root2, (2 + val + valx - valy) % 2);
			}

		}

		if (flag)
		{
			Pcnt = 0;
			for (int i = 1; i<=p+q; i++)
			{
				if (i == Pre[i])
				{
					P[Pcnt][0] = B[i][0];
					P[Pcnt][2] = i;//root
					P[Pcnt++][1] = B[i][1];
				}

			}

			//接下来的步骤就是看看组合的可能性是否唯一,使用背包的思想,只要能填出唯一的p来就有解
			if (flag)
			{

				memset(dp, 0, sizeof(dp));
				memset(dpans, 0, sizeof(dpans));
				memset(dpcnt, -1, sizeof(dpcnt));
				dp[0] = 1;
				dpcnt[0] = 0;
				for (int i = 0; i<Pcnt; i++)//01背包
				{
					for (int j = p; j >= 0; j--)
					{
						int tt = 0;
						for (int k = 0; k<2; k++)//两个物品
						{
							int t = j - P[i][k];
							if (t >= 0 && dp[t] &&dpcnt[t] == i)
							{
								tt += dp[t];
								dpans[i][j][0] = t;
								dpans[i][j][1] = i;
								dpans[i][j][2] = k;
							}
						}
						if (tt)
						{
							dp[j] = tt;
							dpcnt[j] = i + 1;
						}
					}
				}
				if (dp[p] == 1&&dpcnt[p]==Pcnt)
				{
					Ans.clear();
					int b = p;
					int a = Pcnt - 1;
					while (b)
					{

						int theroot = P[dpans[a][b][1]][2];
						int theval = dpans[a][b][2];
						b = dpans[a][b][0];
						a--;
						for (int i = 1; i<=p+q; i++)
						{
							
								int val;
								int root = find(i, val);
								if (root == theroot&&val == theval)
								{
									Ans.push_back(i);
								}
						}



					}
					sort(Ans.begin(), Ans.end());
					for (int i = 0; i<Ans.size(); i++)
					{
						cout << Ans[i] << endl;
					}
					cout << "end" << endl;
				}
				else
				{
					flag = false;
				}
			}
		}
		if (!flag)
			cout << "no" << endl;
	}

	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值