POJ 1417 True Liars(路径压缩并查集+DP背包问题)

POJ 1417 True Liars(路径压缩并查集+DP背包问题)

http://poj.org/problem?id=1417

题意:

        给出p1+p2个人,其中p1个是好人,p2个是坏人。然后有一些关系 ,a说b是好人(坏人).其中没有矛盾的,判断是否有唯一解判断哪些人是好人,哪些人是坏人。

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

分析:

        如果A说B是好人,那么A与B是同一类人。如果A说B是坏人,那么A与B不同类。(想想为什么是这样)

        所以原题所给的每句话就是一条路径压缩并查集的关系,那么我们最终合并所有关系可以得到cnt个连通分量且每个分量中节点的相互关系(同类or异类)我们都知道。

        好,现在假设有cnt个连通分量,第一个分量有x1与y1个两类人(我们不知道到底x1那些人是好人还是y1那些人是好人),第二个分量有x2与y2个两类人...所以我们现在想知道是否只有一种方式让我们从第一分量中抓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]] }

 AC代码:

<span style="font-size:18px;">#include<cstdio>
#include<cstring>
#include<map>
using namespace std;
const int MAXN=1000;
int bag[MAXN][2];//bag[i][0]表示第i(重新编号了)个连通分量的0类人的个数,bag[i][1]表1类人
map<int,int> mp;//用来将老的连通分量编号映射bag中的新编号
int cnt;//一共有cnt个分量
int insert(int x,int b)//连通分量x,与x的关系是b(0表示同类,1表示异类)
{
    if(mp.find(x)==mp.end())mp[x]=++cnt;
    bag[mp[x]][b]++;//该分量的b类人加1
    return mp[x];
}
int F[MAXN];
int v[MAXN];//表示i与根的关系
int findset(int i)
{
    if(F[i]==-1)return i;
    int temp= findset(F[i]);
    v[i] =(v[i]+v[F[i]])%2;
    return F[i]=temp;
}
void bind(int i,int j,int temp)
{
    int fa=findset(i);
    int fb=findset(j);
    if(fa!=fb)
    {
        F[fb]=fa;
        v[fb]=(v[i]+v[j]+temp)%2;
    }
}
int d[MAXN][310];//DP
int main()
{
    int n,p1,p2;
    while(scanf("%d%d%d",&n,&p1,&p2)==3)
    {
        if(n==0&&p1==0&&p2==0)break;
        cnt=0;
        mp.clear();
        memset(bag,0,sizeof(bag));
        memset(F,-1,sizeof(F));
        memset(v,0,sizeof(v));
        memset(d,0,sizeof(d));
        while(n--)
        {
            int a,b,temp;
            char str[10];
            scanf("%d%d%s",&a,&b,str);
            if(str[0]=='y')
                temp=0;
            else if(str[0]=='n')
                temp=1;
            int fa=findset(a);
            int fb=findset(b);
            if(fa!=fb)//不同分量
                bind(a,b,temp);;
        }
        for(int i=1;i<=p1+p2;i++)//将1到p1+p2所有点重新编号
        {
            int fi=findset(i);
            insert(fi,v[i]);
        }
        d[0][0]=1;//初值
        for(int i=1;i<=cnt;i++)//连通分量编号从1到cnt
        {
            for(int j=0;j<=p1;j++)
            {
                if( j>=bag[i][0] )
                    d[i][j] = d[i-1][j-bag[i][0]];
                if( j>=bag[i][1] )
                    d[i][j] += d[i-1][ j-bag[i][1] ];
            }
        }
        //printf("###%d\n",d[cnt][p1]);

        if(d[cnt][p1]==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( d[k][j] == d[k-1][j-bag[k][0]] )
                {
                    choose[k]=0;
                    j=j-bag[k][0];
                }
                else if( d[k][j] == d[k-1][j-bag[k][1]] )
                {
                    choose[k]=1;
                    j=j-bag[k][1];
                }
            }
            for(int i=1;i<=p1+p2;i++)
            {
                int fa=findset(i);//找出分量的编号fa
                int num=mp[fa];//找出fa重新编号后的编号 num
                if(v[i]==choose[num])
                    printf("%d\n",i);
            }
            printf("end\n");
        }
        else
        {
            printf("no\n");
        }

    }
    return 0;
}
</span>

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 给出一个$n\times m$的矩阵,每个位置上有一个非负整数,代表这个位置的海拔高度。一开始时,有一个人站在其中一个位置上。这个人可以向上、下、左、右四个方向移动,但是只能移动到海拔高度比当前位置低或者相等的位置上。一次移动只能移动一个单位长度。定义一个位置为“山顶”,当且仅当从这个位置开始移动,可以一直走到海拔高度比它低的位置上。请问,这个矩阵中最多有多少个“山顶”? 输入格式 第一行两个整数,分别表示$n$和$m$。 接下来$n$行,每行$m$个整数,表示整个矩阵。 输出格式 输出一个整数,表示最多有多少个“山顶”。 样例输入 4 4 3 2 1 4 2 3 4 3 5 6 7 8 4 5 6 7 样例输出 5 算法1 (递归dp) $O(nm)$ 对于这道题,我们可以使用递归DP来解决,用$f(i,j)$表示以$(i,j)$为起点的路径最大长度,那么最后的答案就是所有$f(i,j)$中的最大值。 状态转移方程如下: $$ f(i,j)=\max f(x,y)+1(x,y)是(i,j)的下一个满足条件的位置 $$ 注意:这里的状态转移方程中的$x,y$是在枚举四个方向时得到的下一个位置,即: - 向上:$(i-1,j)$ - 向下:$(i+1,j)$ - 向左:$(i,j-1)$ - 向右:$(i,j+1)$ 实现过程中需要注意以下几点: - 每个点都需要搜一遍,因此需要用双重for循环来枚举每个起点; - 对于已经搜索过的点,需要用一个数组$vis$来记录,防止重复搜索; - 在进行状态转移时,需要判断移动后的点是否满足条件。 时间复杂度 状态数为$O(nm)$,每个状态转移的时间复杂度为$O(1)$,因此总时间复杂度为$O(nm)$。 参考文献 C++ 代码 算法2 (动态规划) $O(nm)$ 动态规划的思路与递归DP类似,只不过转移方程和实现方式有所不同。 状态转移方程如下: $$ f(i,j)=\max f(x,y)+1(x,y)是(i,j)的下一个满足条件的位置 $$ 注意:这里的状态转移方程中的$x,y$是在枚举四个方向时得到的下一个位置,即: - 向上:$(i-1,j)$ - 向下:$(i+1,j)$ - 向左:$(i,j-1)$ - 向右:$(i,j+1)$ 实现过程中需要注意以下几点: - 每个点都需要搜一遍,因此需要用双重for循环来枚举每个起点; - 对于已经搜索过的点,需要用一个数组$vis$来记录,防止重复搜索; - 在进行状态转移时,需要判断移动后的点是否满足条件。 时间复杂度 状态数为$O(nm)$,每个状态转移的时间复杂度为$O(1)$,因此总时间复杂度为$O(nm)$。 参考文献 C++ 代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值