zoukankan      html  css  js  c++  java
  • 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
    ", i);
                    }
                }
                printf("end
    ");
            }
            else
                printf("no
    ");
        }
        return 0;
    }
    View Code

     

  • 相关阅读:
    css实现强制不换行/自动换行/强制换行
    提供一个跨浏览器的XML DOM对象解决方案,来自于《javascript高级程序设计》
    Javascript的IE和Firefox兼容性汇编收藏.txt
    弹出一个层来让用户确认操作(转)
    弹窗代码
    C# 绘制统计图(柱状图, 折线图, 扇形图) (转)
    把Sql类型转换为C#类型的函数
    类型对象和类实例对象
    javascript 父窗体获取子窗体操作结果
    类型转换(二):是使用 is 还是 as
  • 原文地址:https://www.cnblogs.com/wd-one/p/4454549.html
Copyright © 2011-2022 走看看