【题目链接】 http://www.lydsy.com/JudgeOnline/problem.php?id=2466
【题目大意】
给定一棵树,每个节点有一盏指示灯和一个按钮。如果节点的按扭被按了,
那么该节点的灯会从熄灭变为点亮(当按之前是熄灭的),或者从点亮到熄灭
并且该节点的直接邻居也发生同样的变化。开始的时候,所有的指示灯都是熄灭的。
请编程计算最少要按多少次按钮,才能让所有节点的指示灯变为点亮状态。
【题解】
高斯消元枚举自由变元回代。
【代码】
#include <cstdio> #include <algorithm> #include <cstring> using namespace std; namespace Gauss{ const int N=110,MOD=2,INF=1e9; int a[N][N],ans[N]; bool isFreeX[N]; int inv(int a,int m){return(a==1?1:inv(m%a,m)*(m-m/a)%m);} int getAns(int n,int m,int r){ int res=0; for(int i=r-1;~i;i--){ for(int j=0;j<m;j++){ if(!a[i][j])continue; ans[j]=a[i][m]; for(int k=j+1;k<m;k++){ ans[j]-=a[i][k]*ans[k]; ans[j]%=MOD; if(ans[j]<0)ans[j]+=MOD; } ans[j]=ans[j]*inv(a[i][j],MOD)%MOD; break; } } for(int i=0;i<m;i++)res+=ans[i]; return res; } int gauss(int n,int m){ for(int i=0;i<m;i++)isFreeX[i]=0; int r=0,c=0; for(;r<n&&c<m;r++,c++){ int maxR=r; for(int i=r+1;i<n;i++)if(abs(a[i][c])>abs(a[maxR][c]))maxR=i; if(maxR!=r)swap(a[maxR],a[r]); if(!a[r][c]){r--;isFreeX[c]=1;continue;} for(int i=r+1;i<n;i++){ if(a[i][c]){ int delta=a[i][c]*inv(a[r][c],MOD); for(int j=c;j<=m;j++){ a[i][j]-=delta*a[r][j]; a[i][j]%=MOD; if(a[i][j]<0)a[i][j]+=MOD; } } } } for(int i=r;i<n;i++)if(a[i][m])return -1; return r; } // 模2枚举自由变元 int getMinAns(int n,int m,int r){ int res=INF,freeX=m-r; for(int s=0;s<1<<freeX;s++){ if(__builtin_popcount(s)>=res)continue; int cnt=0; for(int j=0;j<m;j++){ if(isFreeX[j]){ ans[j]=s>>cnt&1; ++cnt; } }res=min(res,getAns(n,m,r)); }return res; } } int n,x,y; int main(){ while(~scanf("%d",&n),n){ using namespace Gauss; memset(a,0,sizeof(a)); for(int i=1;i<n;i++){ scanf("%d%d",&x,&y); a[x-1][y-1]=1; a[y-1][x-1]=1; } for(int i=0;i<n;i++)a[i][i]=a[i][n]=1; int r=gauss(n,n); printf("%d ",getMinAns(n,n,r)); }return 0; }