POJ1417

来源:互联网 发布:json图片解析 编辑:程序博客网 时间:2024/06/05 23:05
#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是有效Bint 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;//rootP[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;}

原创粉丝点击