poj1417 true liars(并查集 + DP)详解

这个题做了两天了。首先用并查集分类是明白的, 不过判断是否情况唯一刚开始用的是搜索。总是超时。 后来看别人的结题报告, 才恍然大悟判断唯一得用DP.

题目大意:

一共有p1+p2个人,分成两组,一组p1个,一组p2个。给出N个条件,格式如下:

x y yes表示x和y分到同一组

x y no表示x和y分到不同组

问分组情况是否唯一,若唯一则按从小到大顺序输出,否则输出no。保证不存在矛盾条件,但是有可能出现x=y的情况。

题目分析: 题中会给我们一些信息, 告诉我们那些是同一类, 哪些是不同类。 当然刚开始的时候我们无法判断那一类是好人、坏人。 那么我们不妨把有关系的点(无论他们的关系是yes还是 no)全归为一类, 他们有一个相同的父节点。然后用一个数组(relation[])记录他与父节点的关系(0代表同类, 1代表异类)。当然因为所给的只是一部分信息, 所以有可能无法把所有点归为一类(例如:1,2 yes 3,4 yes。只表明1,2同类 , 3,4同类, 1,3的关系并不知道)。那么不妨设几个不同的集合(以父节点为划分标准)每个集合分为两类 ,与父节点同类(relation[] = 0), 与父节点不同类(relation[] = 1)。此时我们问题转变为答案是否唯一。取每个集合中的一种类型,且仅取一种(同类或不同类)。看累计人数得p1的情况是否唯一。

#include<iostream>
#include<cstdio>
#include<string.h>
using namespace std;

int n, n1, n2, mi, mx, key, flag, a[605][2], vis[605][605][2], ans[605][2], v[605], d[605][605], pre[610], relation[605];
int find(int x)//寻找最根部的父亲节点
{
    if(pre[x] == x)
        return x;
    else if(pre[x] != x)
    {
        int t = pre[x];
        pre[x] = find(pre[x]);//边寻找最根部父亲节点,边更新原父亲节点的信息
        relation[x] = (relation[t] + relation[x]) % 2;//类似于种类并查集的更新
    }
    return pre[x];
}
void work(int a, int b, int c)//合并节点
{
    int fx = find(a);
    int fy = find(b);
    if(fx != fy)
    {
        pre[fx] = fy;
        relation[fx] = (relation[a] + relation[b] + c) % 2;
    }
}

void dp()
{
    int k = 1;
    for(int i = mi+1; i <= (n1+n2); i++)
    {
        if(v[i] == 1)
        {
            k++;
            int t1 = ans[i][0];
            int t2 = ans[i][1];
            int mx = min(t1, t2);
            for(int j = n1; j >= mx; j--)
            {
                d[k][j] = d[k-1][j-t1] + d[k-1][j-t2];
                if(d[k-1][j-t1] == 1 && d[k-1][j-t2] == 0)
                {
                    vis[k][j][0] = i;
                    vis[k][j][1] = 0;
                }
                else if(d[k-1][j-t2] == 1 && d[k-1][j-t1] == 0)
                {
                    vis[k][j][0] = i;
                    vis[k][j][1] = 1;
                }
            }
        }
    }
}
int main()
{
    while(scanf("%d%d%d", &n, &n1, &n2) != EOF)
    {
        if(n == 0 && n1 == 0 && n2 == 0)
            break;
        //初始化所有节点得父节点和relation
        for(int i = 1; i <= n1+n2; i++){pre[i] = i; relation[i] = 0;}
        for(int i = 1; i <= n; i++)
        {
            int a, b;
            char c[10];
            scanf("%d%d%s", &a, &b, &c);
            if(strcmp(c, "yes") == 0)
                work(a, b, 0);
            else if(strcmp(c, "no") == 0)
                work(a, b, 1);
        }
        //这里注意一下:到这一步,有可能存在一些点的父节点不是最跟的节点
        for(int i = 1; i <= n1+n2; i++)
            int t = find(i);
        memset(v, 0, sizeof(v));
        //ans[i][0]表示与最根节点i同类的节点个数, ans[i][1]代表与最根节点i不同类的节点个数
        memset(ans, 0, sizeof(ans));
        flag = 0;//存储一共有多少个不同的最跟部的父节点
        mi = 10e9;
        for(int i = 1; i <= n1+n2; i++)
        {
            int x = pre[i];
            int y = relation[i];
            if(x < mi)
                mi = x;
            ans[x][y]++;
            if(v[x] == 0)
            {
                v[x] = 1;
                flag++;
            }
        }
        /*d[i][j]前i个集合中累计人数为j时有多少种可能, vis[i][j][]保存每一个集合选了哪一类, 为最后输出用。 vis[i][j][0]表示前i个集合中累计人数为j时的最根节点,vis[i][j][1] 表示前i个集合中累计人数为j时,最根节点为vis[i][j][0]时,与根节点的关系*/
        memset(d, 0, sizeof(d));
        memset(vis, 0, sizeof(vis));
        d[1][ans[mi][0]]++; d[1][ans[mi][1]]++;
        vis[1][ans[mi][0]][0] = mi;
        vis[1][ans[mi][0]][1] = 0;
        vis[1][ans[mi][1]][0] = mi;
        vis[1][ans[mi][1]][1] = 1;
        dp();
        if(d[flag][n1] == 1)//如果前flag个集合中累计人数为n1的可能为1时,有唯一解
        {
            int j = n1;
            for(int i = flag; i >= 1; i--)//从后往前推
            {
                a[i][0] = vis[i][j][0];
                a[i][1] = vis[i][j][1];//记录第i个集合中取得是哪一类(同类0, 不同类1)
                j -= ans[a[i][0]][a[i][1]];
            }
            for(int i = 1; i <= n1+n2; i++)
            {
                for(int j = 1; j <= flag; j++)
                {
                    int f = pre[i];
                    int ff = relation[i];
                    if(f == a[j][0] && ff == a[j][1])
                        printf("%d\n", i);
                }
            }
            printf("end\n");
        }
        else
            printf("no\n");
    }
    return 0;
}

时间: 2024-10-18 23:00:14

poj1417 true liars(并查集 + DP)详解的相关文章

并查集算法详解

更好的阅读体验 并查集算法详解 算法详解 维护类型 身为一个数据结构,我们的并查集,它的维护对象是我们的关注点. 并查集适合维护具有非常强烈的传递性质,或者是连通集合性质. 性质详解 传递性质 传递性,也就是具有传递效应的性质,比如说A传递给B一个性质或者条件,让B同样拥有了这个性质或者条件,那么这就是我们所说的传递性. 连通集合性质 连通集合性,和数学概念上的集合定义是差不多的, 比如说A和B同属一个集合,B和C同属一个集合,那么A,B,C都属于同一个集合.这就是我们所谓的连通集合性质. 算法

POJ 1417 True Liars 并查集+背包

题目链接:http://poj.org/problem?id=1417 解题思路:比较容易想到的是并查集,然后把第三组数据测试一下之后发现这并不是简单的并查集,而是需要合并之后然后判断的.并且鉴于题目要求输出数据,因此还要记录数据,可以说是非常有意思的题目. 首先,如果a b yes,那么a与b一定都是圣人或者恶人:反之,如果a b no,那么两者一个圣人,一个恶人.因此可以将所有元素分为若干个集合,每个集合中放两个小集合,表示两种不一样的人,当然并不知道到底哪个里面是圣人,恶人. 另一个比较棘

并查集--学习详解

[转] 文章作者:yx_th000 文章来源:Cherish_yimi (http://www.cnblogs.com/cherish_yimi/) 昨天和今天学习了并查集和trie树,并练习了三道入门题目,理解更为深刻,觉得有必要总结一下,这其中的内容定义之类的是取自网络,操作的说明解释及程序的注释部分为个人理解.并查集学习: l         并查集:(union-find sets) 一种简单的用途广泛的集合. 并查集是若干个不相交集合,能够实现较快的合并和判断元素所在集合的操作,应用很

poj1308 Is It A Tree?(并查集)详解

poj1308   http://poj.org/problem?id=1308 题目大意:输入若干组测试数据,输入 (-1 -1) 时输入结束.每组测试数据以输入(0 0)为结束标志.然后根据所给的所有(父亲, 孩子)数据对判断 是否能构成一棵树. 分析: 都以了解树只有一个根节点,那么我们就判断是不是有多个树:又知道每个节点只有一个父亲节点,那么我们就判断他是不是构成环,成环则不是树. 注意: ①可以是空树: ②所给的节点构成森林(多个树)是不可以的,必须只能构成一棵树. #include<

poj1182 食物链(种类并查集)详解

poj 1182   http://poj.org/problem?id=1182 分析:这个题大意说的非常清楚了,就是求出假话的个数,题目中给的假话要求有三个 ① 当前的话与前面的某些真的话冲突,是假话: ②当前的话中X或Y比N大,是假话: ③当前的话表示X吃X,是假话. ②和③很好判断了,最难的就是假话条件①啦!!    题中说有三种动物A,B,C:   A-->B-->C-->A(A吃B, B吃C,C又吃A), 形成一个环: 然而我们又没办法把所给的动物(数字代替)确切的分给哪一类

poj1417(种类并查集+dp)

题目:http://poj.org/problem?id=1417 题意:输入三个数m, p, q 分别表示接下来的输入行数,天使数目,恶魔数目: 接下来m行输入形如x, y, ch,ch为yes表示x说y是天使,ch为no表示x说y不是天使(x, y为天使,恶魔的编号,1<=x,y<=p+q):天使只说真话,恶魔只说假话: 如果不能确定所有天使的编号,输出no,若能确定,输出所有天使的编号,并且以end结尾: 注意:可能会有连续两行一样的输入:还有,若x==y,x为天使: 思路:种类并查集+

真正的骗子(并查集+dp+dp状态回溯)

[//]: # (推荐题解模板,请替换blablabla等内容 ^^) ### 题目描述 一个岛上存在着两种居民,一种是天神,一种是恶魔. 天神永远都不会说假话,而恶魔永远都不会说真话. 岛上的每一个成员都有一个整数编号(类似于身份证号,用以区分每个成员). 现在你拥有n次提问的机会,但是问题的内容只能是向其中一个居民询问另一个居民是否是天神,请你根据收集的回答判断各个居民的身份. 输入格式 输入包含多组测试用例. 每组测试用例的第一行包含三个非负整数n,p1,p2p1,p2,其中n是你可以提问

Centos7安装mariadb galera cluster数据库集群 & 详解

#Galera集群特点 集群之间无延时,同步复制.而master-slave主从异步复制,存在延迟. active-active多主,集群内部服务器都是同时写,必须等所有集群内所有数据库都完成数据写入,才会反馈完成,所以不存在数据丢失的情况. 集群节点自动故障转移,如果集群中单个节点故障,失效节点会自动被清除. 扩展方便,只要将新的节点添加到集群,新节点自动复制数据. #Galera集群原理     #主要通过galera插件保证数据的一致性,该数据复制的过程是可认证的复制,原理如下: #解析

Linux集群详解

Linux集群详解 集群或者说是群集:其目的是为了实现将多台计算机组合以来完成特定的任务,比如天气预报,大型网络游戏,这些都需要很大的运算量,单台计算机实现成本太高,而且不显示.那么就需要通过集群的方式,将废弃的或者正在使用的计算机联合起来,结合整体的力量来解决这些问题 集群类型: 1.  负载均衡集群 load blancing ,简称LB 2.  高可用性集群 high availibility,简称 HA 3.  高性能集群 high performance,简称 HP 作用: 1.  负

Openfire Hazelcast集群详解

Openfire Hazelcast集群详解 作者:chszs,版权所有,未经同意,不得转载.博主主页:http://blog.csdn.net/chszs 一.概述 Openfire Hazelcast插件提供了在一个集群上运行多个冗余Openfire服务器的支持.通过把Openfire运行为一个集群,可以把终端的连接分配到多台Openfire服务器上,同时还提供了服务器的故障转移.Hazelcast个插件是Openfire原集群插件的替代,它使用了开源的Hazelcast数据分布框架来代替昂