题解 P3429 【[POI2005]DWA-Two Parties】

oscar

2017-09-22 18:32:40

Solution

【POI补全计划#13 POI2005 DWA】 首先照例吐槽翻译 题目的意思是将一张无向图的点分成两个集合,去掉所有跨越集合的边,使得尽量多的点度数为偶数,输出分到其中一个集合中的所有点,有SPJ 题面里的“允许一小部分人没有参加派对”和“并且尽量使一个居民在派对上的朋友数更多”都是骗人的 还是要仔细读英文题面 以下是题解 -----------------------------分割线----------------------------- 首先随便手画几个图 ```plain (1)---(2) | \ | | \ | | \ | (3)---(4) ``` 之类的 手玩发现所有(画出来的)图都存在一种分割方案使得所有点剩余度数都为偶数(大胆猜想,不需证明) 然后考虑怎么输出方案 记第i个点度数为deg[i],与第i个点有边相连的点构成的集合为adj[i],某些数的异或和为XOR{条件}{内容} 记x[i]为i所属集合(0或1),按如下方式列方程 若deg[i]为偶数,则(XOR{j属于adj[i]}{x[j]}) == 0,即与i相邻的节点有偶数个在集合1中(这个公式可以对i分情况讨论得到) 否则deg[i]为奇数,则(XOR{j属于adj[i]}{x[j]}) xor x[i] == 1,即i节点和与i相邻的节点有奇数个在集合1中 这样就构造出来了n个未知数和n个方程 高斯消元搞一下就可以把方程解出来,然后输出所有x[i]==1的i就解决了 注意方程有多解,需要自己思考细节 贴代码: ```cpp #include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> using namespace std; int deg[233],mat[233][233],val[233]; int main() { int n,tmp; scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",& /* 这里有个luogu的bug,如果去掉这段注释的话代码会变为一个符号(°) */ deg[i]); for(int j=1;j<=deg[i];j++) { scanf("%d",&tmp); mat[i][tmp]=1; } if(deg[i]&1)mat[i][i]=val[i]=1; } for(int i=1;i<=n;i++) { int sw=0; for(int j=i;j<=n;j++) { if(mat[j][i]==1) { sw=j; break; } } if(sw) { for(int j=i;j<=n;j++) { swap(mat[i][j],mat[sw][j]); } swap(val[i],val[sw]); } for(int j=i+1;j<=n;j++) { if(mat[j][i]==0)continue; for(int k=i;k<=n;k++) { mat[j][k]^=mat[i][k]; } val[j]^=val[i]; } } mat[n][n]=1; for(int i=n-1;i>=1;i--) { for(int j=i+1;j<=n;j++) { if(mat[i][j]) { for(int k=j;k<=n;k++) { mat[i][k]^=mat[j][k]; } val[i]^=val[j]; } } mat[i][i]=1; } int sum=0; for(int i=1;i<=n;i++)if(val[i])sum++; printf("%d\n",sum); for(int i=1;i<=n;i++)if(val[i])printf("%d ",i); return 0; } ```