1230.Honest Village and Lie Village 【带权并查集】【01背包】【输出路径】

1230.Honest Village and Lie Village

Time Limit: 1000 MS    Memory Limit: 131072 KB
Total Submission(s): 5    Accepted Submission(s): 4

Description

The Honest Village and The Lie Village’s story you should have heard of.You want to go to a village, the village is the honest village, all the people there are only to tell the truth, now in the way to go, encountered a fork in the road, the road to your destination honest village, and another road leads to another a village called lie village, where people are just lying. Now two men stood at the crossroads where (one of them come from honest village, another is from lie village,), you can ask them two honest village where the village road is determined to be honest, what would you ask them, how to distinguish the two of them who is from the honest village and who is from the lie village?

The method is just pointing to a village to ask two people: "Where are you from?" if that is honest village, two elderly people will say “yes”; if the answer is "no" means that is the lie village. This way can know which is the honest village.

Now change the question, there is a group of people in front of lmh, they are from honest village or lie village, and they know each other from where. lmh can only ask someone from where (for example, A, B two people, you can ask A, where is B from, if from honest village, then return to 1, or return to 0). A and B can be the same person. Now according to query to judge all the people come from which village, if you can, export all from honest village, otherwise output “no”.

 

Input

The first line has three non-negative integers n, p, and q. n is the number of questions you asked. p and q are the populations of the honest village and lie village. Each of the following n lines has two integers xi, yi and ai. xi and yi are the identification numbers of inhabitants, each of which is between 1 and p + q. (1<=n<=1000,1<=p+q<=300)

 

Output

For each data set, if it includes sufficient information to classify all people, print the identification numbers of all people from honest village in ascending order, one in a line. Otherwise, i.e., if a given data set does not include sufficient information to identify all people, print no in a line.

 

Sample Input

2 2 1
1 2 1
2 3 0
3 2 1
1 1 1
2 2 1
3 3 1

Sample Output

1
2
no

Source

Unknown

1230.诚实村和谎言村

时间限制: 1000 MS 内存限制: 131072 KB
提交总数:5份接受提交:4

描述

诚实的村庄和谎言村的故事,你应该听说过。你想去一个村庄,这个村庄是诚实的村庄,所有的人只有说实话,现在在去的路上,遇到了一个岔路口,一条路通向诚实村,所有的人都说实话,另一条路通向谎言村,那里的人们总是在撒谎。现在两人站在十字路口,(其中一个来自诚实村,另一个是来自谎言村),你可以问他们两个那条路去诚实村,那么如何区分他们两个谁是诚实的村庄和谎言的村庄是谁?

方法只是指着一个村庄问两个人:"你来自哪里?如果那是诚实村,两个人会说"yes"如果答案是"no",那就是谎言村。这样就能知道哪个是诚实的村庄了。

现在换个问题,有一群人在lmh面前,他们来自诚实的村庄或谎言村,他们互相知道对方从哪里。lmh只能问其中一个人从何而来(例如,A、B两个人,你会问A,B来自哪里,如果B来自诚实的村庄,然后返回1,否则返回0)。A 和 B 可以是同一个人。

现在根据查询判断所有的人都来自哪个村庄,如果有答案的话,输出全部来自诚实的村庄的人,否则输出"no"。




输入

第一行有三个正整数 n、p 、q。n 是您提出的问题数。p和q是诚实的村庄和谎言村的人口。下面 n 行中的每一行都有三个整数 xi、yi 和 ai。xi 和 yi 是居民的身份编号,每个标识号介于 1 和 p = q. (1<=n<=1000,1<=p+q<=300)




输出

对于每个数据集,如果包含足够的信息来对所有人员进行分类,则按升序打印来自诚实村庄的所有人员的识别号,一行。否则,即,如果给定的数据集不包含足够的信息来标识所有人员,请在行中打印 no。




示例输入

2 2 1

1 2 1

2 3 0

3 2 1

1 1 1

2 2 1

3 3 1

样本输出

1

2

no

未知

 

刚开始做这道题没有思路 所以用了一天刷完了kuangbin的并查集专题
回过头来再看的时候 第一感觉是带权并查集 第二感觉是刚做过。
于是发现这道题是 POJ1417 改编过来的😀
重新整理一下他的思路:

题意:

        给出p1+p2个人,其中p1个来自诚实村,p2个来自谎言村。然后有一些关系 ,a说出b来自哪一个村子,判断是否有唯一解能够判断出哪些人来自诚实村,哪些人来自谎言村。

        其中比较重要的是,诚实村总说真话,谎言村总说假话(这句话的特殊意义)。不会存在矛盾情况。请问你是否存在唯一解,如果存在请输出唯一解。

分析:

        如果A说B来自诚实村,那么A与B是同一类人。如果A说B来自谎言村,那么A与B不同类。(好好思考一下)

A B 结果
诚实 诚实 AB同村
诚实 谎言 AB异村
谎言 谎言 AB同村
谎言 诚实 AB异村

 

       

 

 

  

  所以原题所给的每句话就是一条带权并查集的关系,那么我们最终合并所有关系可以得到cnt个关系集合且每个关系集合中节点的相互关系(同村or异村)我们都知道。

        好,现在假设有cnt个集合,

  第一个集合有x1与y1个两类人(我们不知道到底x1那些人是好人还是y1那些人是好人),

  第二个集合有x2与y2个两类人

  ……

  第n个集合有Xn与Yn个两类人

  所以我们现在想知道是否有且只有一种方式让我们从第一个集合中抓出X1(或Y1)个人,从第二个集合中抓出X2(或Y2)人...直到每个集合都抓一类人时,正好抓了p1个人。

        如果只有1种方式实现上面的目的,那么就有唯一解。记录DP的每次选择最后输出即可。

        令d[i][j]表示取完前i个集合后正好j人的方法数,我们最后要求的是看d[cnt][p1]是否==1?

        DP转移方程:dp[i][j]=sum{ dp[i-1][j-bag[i][0]]+dp[i-1][j-bag[i][1]] }

  注意:每个人与根节点的关系 用0代表相同 1代表不同更好做一些。

 

 AC代码:

#include<iostream>
#include<cstdio>
#include<map>
#include<cstring>
#define ll long long
using namespace std;
const int MAXN=1000;
int pre[MAXN+50];///表示每个人的父亲是谁
int rel[MAXN+50];///表示每个人与根节点的关系
map<int,int>mp;///用来将每个人的编号映射bag中的新集合编号
int bag[MAXN+50][2];///bag[i][0]表示第i个集合中(0类人——诚实村或谎言村)有x个人
                    ///bag[i][1]表示第i个集合中(1类人——谎言村或诚实村)有x个人
int cnt;
int dp[MAXN+50][300+50];
int Find(int i)
{
    if(pre[i]==-1)return i;
    int tmp=Find(pre[i]);
    rel[i]=(rel[i]+rel[pre[i]])%2;
    return pre[i]=tmp;
}
void Union(int x,int y,int tmp)
{
    int fx=Find(x);
    int fy=Find(y);
    if(fx!=fy)
    {
        pre[fy]=fx;
        rel[fy]=(rel[x]+rel[y]+tmp)%2;
    }
}
void init()
{
    memset(bag,0,sizeof(bag));
    mp.clear();
    memset(pre,-1,sizeof(pre));
    memset(rel,0,sizeof(rel));
    memset(dp,0,sizeof(dp));
}
int main()
{
    int n,p1,p2;
    while(scanf("%d %d %d",&n,&p1,&p2)!=EOF)
    {
        if(n==0&&p1==0&&p2==0)
        {
            break;
        }
        cnt=0;
        init();
        int a,b,tmp;
        for(int i=0;i<n;i++)
        {
            scanf("%d %d %d",&a,&b,&tmp);
            tmp^=1;///注意 这里要异或一下
            int fa=Find(a);
            int fb=Find(b);
            if(fa!=fb)
            {
                Union(a,b,tmp);
            }
        }
        for(int i=1;i<=p1+p2;i++)///遍历每一个人
        {
            int fi=Find(i);///找到父亲
            if(mp.find(fi)==mp.end())mp[fi]=++cnt;///如果集合不存在 用mp记录新集合的编号
            bag[mp[fi]][rel[i]]++;///bag中相应集合人数++
        }
        dp[0][0]=1;///只能有一种方案
        for(int i=1;i<=cnt;i++)
        {
            for(int j=0;j<=p1;j++)
            {
                if(j>=bag[i][0])dp[i][j]+=dp[i-1][j-bag[i][0]];
                if(j>=bag[i][1])dp[i][j]+=dp[i-1][j-bag[i][1]];
            }
        }
        if(dp[cnt][p1]==1)///这个1能到终点 并且终点是1 表示能区分
        {

            int j=p1;
            int choose[MAXN];///choose[i]=1/0表示第i(重新编号)个连通集合选择第0类还是选第1类
            memset(choose,-1,sizeof(choose));
            for(int k=cnt;k>=1;k--)///逆推找出choose
            {
                if( dp[k][j] == dp[k-1][j-bag[k][0]] )
                {
                    choose[k]=0;///第0个阵容的人是诚实村
                    j=j-bag[k][0];
                }
                else if( dp[k][j] == dp[k-1][j-bag[k][1]] )
                {
                    choose[k]=1;///第1个阵容的人是诚实村
                    j=j-bag[k][1];
                }
            }
            for(int i=1;i<=p1+p2;i++)
            {
                int fa=Find(i);///找出分量的编号fa
                int num=mp[fa];///找出fa重新编号后的编号 num
                if(rel[i]==choose[num])///这个人与根节点的关系恰好是第X个阵容
                    cout<<i<<'\n';
            }
        }
        else
        {
            cout<<"no"<<'\n';
        }
    }
    return 0;
}

 

posted @ 2019-08-15 10:14  观稳769  阅读(186)  评论(0编辑  收藏  举报