poj1417 并查集+背包

来源:互联网 发布:张子凡 陆林轩 知乎 编辑:程序博客网 时间:2024/06/07 22:12

题目意思很明确,给出相对关系和各种类人数,让求是否能确定所有人所属的种类,说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 0
原创粉丝点击