今天上午很神奇地被两个dp用小错误卡了一上午。。。
题意:给出每个人是否认识另外的人(可能单向认识),分两组让每组中的人互相认识,使两组人数差最小并输出方案
把每对不是互相认识的人连边,代表他们不能分到一个组,这样的话连通分量可能有很多个,然而各个连通分量之间是互不影响的,所以分别二分图染色就可以,然后统计两种颜色在这个连通分量里分别有多少人。这样就把人分成了k个组,每个组中可以让染黑色的人去Team1,或去Team2,这样是有差别的,比如黑色比白色多2人,如果Team1选了黑色,那么就会比Team2又多两人,反之就会少两人,这像什么?背包!但是取值可能出现负数,不能作为数组下标,那都加上一个大数就好了。bool数组dp[i][j]代表选前i组能否取到j
然后美滋滋地写完,WA,vjudge上还不给数据,还不会手写SPJ,只能自己造数据人眼对拍及其心酸,然后我终于拍出来了!多组数据dp数组没memset...多组数据真的坑,省选day2t1学长花式演示没换行没memset等各种花式爆炸。。。
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> using namespace std; struct Group{ int N0,N1; int val(){ return abs(N0-N1); } }T[200000]; int n,m1,h[2020],C[2200],N1,N0,pre[2010][2010],A[20020],T1[5020],T2[5020]; bool v[2010][200],dp[2010][2010],flag; struct edge{ int next,to; void Add(int Next,int To){ next=Next; to=To; } }q[200200]; void addedge(int x,int y){ q[++m1].Add(h[x],y); h[x]=m1; q[++m1].Add(h[y],x); h[y]=m1; } void Dfs(int x,int c){ int i,y; C[x]=c; if (c==0) N0++; else N1++; for (i=h[x];i;i=q[i].next){ y=q[i].to; if (C[y]!=-1){ if (C[y]!=1-c){ flag=1; return; } continue; } Dfs(y,1-c); if (flag) return; } }//二分图染色 void dfs(int x,int c){ if (C[x]==c) T1[++T1[0]]=x; else T2[++T2[0]]=x; C[x]=-1; int i,y; for (i=h[x];i;i=q[i].next){ y=q[i].to; if (C[y]!=-1) dfs(y,c); } } void Work(){ scanf("%d",&n); m1=0; flag=0; memset(v,0,sizeof(v)); memset(h,0,sizeof(h)); memset(q,0,sizeof(q)); memset(C,-1,sizeof(C)); memset(pre,0,sizeof(pre)); memset(dp,0,sizeof(dp)); T1[0]=T2[0]=0; int i,x,j,k=0; for (i=1;i<=n;i++){ while (cin>>x){ if (!x) break; v[i][x]=1; } } for (i=1;i<=n;i++) for (j=i+1;j<=n;j++) if (!v[i][j]||!v[j][i]) addedge(i,j); for (i=1;i<=n;i++) if (C[i]==-1){ A[++k]=i; N0=N1=0; Dfs(i,0); if (flag){ printf("No solution\n"); return; } T[k].N0=N0; T[k].N1=N1; } dp[0][500]=1;//把每个个下标都+500,避免出现负权下标 for (i=1;i<=k;i++) for (j=350;j<=650;j++) if (dp[i-1][j]){ dp[i][j+T[i].val()]=dp[i][j-T[i].val()]=1; pre[i][j+T[i].val()]=i,pre[i][j-T[i].val()]=-i; } for (i=500;i<=1000;i++) if (dp[k][i]){//dp[k][i]写成dp[n][i]了 N0=i; break; } //因为如果选一个集合能取到最优解,那么选它的补集结果是一样的,所以结果对称,只从一边找就可以 for (i=k;i>=1;i--) if (pre[i][N0]<0){ if (T[i].N0>T[i].N1) dfs(A[i],1); else dfs(A[i],0); N0+=T[i].val(); } else{ if (T[i].N0>T[i].N1) dfs(A[i],0); else dfs(A[i],1); N0-=T[i].val(); } for (i=0;i<=T1[0];i++) printf("%d ",T1[i]); printf("\n"); for (i=0;i<=T2[0];i++) printf("%d ",T2[i]); printf("\n"); } int main(){ int T; scanf("%d",&T); for (int i=0;i<T;i++){ if (i) printf("\n"); Work(); } }