#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;
}
POJ1417
最新推荐文章于 2021-11-07 21:10:12 发布