https://blog.csdn.net/qq_34731703/article/details/54603652
1.转化:yes表示双方同类,否则不同类(真的蔡
题目变为有一些集合,内部分两个集合,现在要从每个大集合里选出一个小集合使得选出的这些集合大小之和恰好为p1,且只能有一种方案(这样才能确定每个元素是谁
所以不用可行性dp,我们记录方案数,设$f[i][j]$为前$i$个拼成$j$的方案数,转移显然
但是要输出方案,由于要求从小到大输出编号,而且每个小集合里有很多元素,所以不记录从哪转移,而是看$f[i-1][j-size]$方案数是否为1,如果是1的话肯定是从这转移来的,然后暴力枚举所有元素,选出属于这个集合的即可
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=1009;
int n,p1,p2;
int fa[maxn],d[maxn],w[2][maxn],v[maxn],p[maxn];
inline int find(int x){
if(x==fa[x])return x;
int rt=find(fa[x]);
d[x]^=d[fa[x]];
return fa[x]=rt;
}
inline void unionn(int x,int y,int k){
int xx=find(x),yy=find(y);
if(xx==yy)return;
fa[xx]=yy;
d[xx]=d[x]^d[y]^k;
}
int f[maxn][maxn];
int main(){
while(scanf("%d%d%d",&n,&p1,&p2) && (n+p1+p2)){
for(int i=1;i<=p1+p2;i++)fa[i]=i;
memset(d,0,sizeof(d));
memset(v,0,sizeof(v));
memset(w,0,sizeof(w));
memset(f,0,sizeof(f));
char s[10];
for(int i=1,x,y;i<=n;i++){
scanf("%d%d%s",&x,&y,s);
if(s[0]=='y')unionn(x,y,0);
else unionn(x,y,1);
}
//暴力找每个小集合
int cnt=0;
for(int i=1;i<=p1+p2;i++)
if(!v[i]){
++cnt;
int ff=find(i);
for(int j=i;j<=p1+p2;j++)
if(find(j)==ff && !v[j])v[j]=1,w[d[j]][cnt]++;
p[cnt]=ff;
}
//dp
f[0][0]=1;
for(int i=1;i<=cnt;i++){
int flr=min(w[0][i],w[1][i]);
for(int j=p1;j>=flr;j--){
if(f[i-1][j-w[0][i]]){
f[i][j]+=f[i-1][j-w[0][i]];
// from[i][j]=j-w[0][i];
}
if(f[i-1][j-w[1][i]]){
f[i][j]+=f[i-1][j-w[1][i]];
// from[i][j]=j-w[1][i];
}
}
}
if(f[cnt][p1]!=1){
printf("no\n");continue;
}
//统计答案
int ans[maxn],tot=0,mj=p1;
for(int i=cnt;i>=1;i--)
if(f[i-1][mj-w[0][i]]==1){
for(int j=1;j<=p1+p2;j++)
if(find(j)==p[i] &&d[j]==0)ans[++tot]=j;
mj-=w[0][i];
}
else{
for(int j=1;j<=p1+p2;j++)
if(find(j)==p[i] && d[j]==1)ans[++tot]=j;
mj-=w[1][i];
}
sort(ans+1,ans+1+tot);
for(int i=1;i<=tot;i++)printf("%d\n",ans[i]);
printf("end\n");
}
}