poj1417 并查集+背包

题目意思很明确,给出相对关系和各种类人数,让求是否能确定所有人所属的种类,说no的两个人必然不在一类,说yes的必然在一类。

种类划分,由于只给了相对关系,很容易想到用种类并查集,区间合并多取几次异或就行了。

划分完成后,形成多个集合,每个集合由两部分组成,分别为与根节点相同的一类和与根节点不同的一类,用一个sum数组记录每个集合中每类人数。

要确定是否能将所有集合划分成两部分,就需要明确两点:

1):每个集合形成的两个小集合每次取且仅能取且必须取一个小集合,将其划分为天使或魔鬼的一部分。

2):如果按上述方法对每个集合取一部分之后形成的划分成的两部分中一部分等于天使人数的策略仅存在一种,那么便能将所有人分为两部分。

特别需注意,每对相对关系必须分为两部分,且总和等于天使人数的策略仅存在一种才能划分。

代码:

#include <iostream>  
#include <cstring>  
#include <string>  
#include <algorithm>  
  
using namespace std;  
  
const int N=1000;  
int father[N];  
int sum[2][N];          //0代表一部分,1代表另一部分  
int rank[N];            //记录与根节点关系  
int dp[N][N];           //用来计算能否出现符合题意的策略  
int mark[N][N];         //用来记录该策略的路径  
  
void init()  
{  
    memset(rank,0,sizeof(rank));  
    for (int i=0;i<N;i++)  
    {  
        father[i]=i;  
        sum[0][i]=1;  
        sum[1][i]=0;  
    }  
}  
  
int find(int x)  
{  
    if (x==father[x])  
        return x;  
    int t=father[x];  
    father[x]=find(father[x]);  
    rank[x]=rank[x]^rank[t];  
    return father[x];  
}  
  
void Union(int a,int b,int k)  
{  
    int x=find(a);  
    int y=find(b);  
    if (x==y)  
        return ;  
    father[y]=x;  
    rank[y]=k^rank[a]^rank[b];          //种类并查集和带权并查集常用手段,x->y=x->a->b->y  
    sum[0][x]+=sum[0^rank[y]][y];  
    sum[1][x]+=sum[1^rank[y]][y];   
}  
  
int min(int a,int b)  
{  
    return a>b?b:a;  
}  
  
int main()  
{  
    int n,p1,p2;  
    while (cin>>n>>p1>>p2&&n+p1+p2)  
    {  
        int a,b;  
        string s;  
        init();  
        for (int i=1;i<=n;i++)  
        {  
            cin>>a>>b>>s;  
            int k;  
            if (s[0]=='y')  
                k=0;  
            else  
                k=1;  
            Union(a,b,k);  
        }  
        int w1[1000];           //记录人数  
        int w2[1000];           //每个集合可划分为两部分,所以用相对的两个数组  
        int p[1000];            //记录每个集合的根节点  
        memset(w1,0,sizeof(w1));  
        memset(w2,0,sizeof(w2));  
        int cnt=1;  
        for (int i=1;i<=p1+p2;i++)  
        {        
            if (i==find(i))  
            {  
                w1[cnt]=sum[0][i];  
                w2[cnt]=sum[1][i];  
                p[cnt]=i;  
                cnt++;  
            }  
        }  
        memset(dp,0,sizeof(dp));  
        dp[0][0]=1;  
        /* 
        for (int i=1;i<cnt;i++) 
            cout<<i<<" "<<w1[i]<<" "<<w2[i]<<endl; 
        */  
        memset(mark,0,sizeof(mark));  
        for (int i=1;i<cnt;i++)  
        {  
            for (int t=p1;t>=w1[i];t--)  
            {  
                if (dp[i-1][t-w1[i]])           //这点注意,由于每个集合必须要取,所以当前状态只能由前一状态推出,再之前的状态无用  
                {  
                    dp[i][t]+=dp[i-1][t-w1[i]];         //记录这种状态的策略数,当前状态策略数由之前状态的策略数确定  
                    mark[i][t]=0;  
                }  
            }  
            for (int t=p1;t>=w2[i];t--)  
            {
                if (dp[i-1][t-w2[i]])           //可以取w1,也可以取w2,但是两者仅能取一部分  
                {  
                    dp[i][t]+=dp[i-1][t-w2[i]];         //同上  
                    mark[i][t]=1;  
                }  
            }  
        }  
        //cout<<dp[cnt-1][p1]<<endl;  
        if (dp[cnt-1][p1]!=1)           //如果不能取到或者取到的策略不止一种,不能划分  
            cout<<"no"<<endl;  
        else  
        {  
            int ans[N];  
            int c=0;  
            cnt--;  
            int ss=p1+p2;  
            while (cnt>0)  
            {  
                //cout<<"AA "<<p1<<endl;  
                int cur=mark[cnt][p1];  
                int pp=p[cnt];  
                for (int i=1;i<=ss;i++)         //记录answer  
                {  
                    if (find(i)==pp&&rank[i]==cur)  
                        ans[c++]=i;  
                }      
                if (cur==0)  
                    p1=p1-w1[cnt];  
                else  
                    p1=p1-w2[cnt];  
                cnt--;      
            }  
            sort(ans,ans+c);  
            for (int i=0;i<c;i++)  
                cout<<ans[i]<<endl;  
            cout<<"end"<<endl;  
        }  
    }  
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值