BZOJ 2466 [中山市選2009]樹(高斯消元)
阿新 • • 發佈:2017-09-12
using bzoj break ble isf 狀態 clas memset c++
【題目鏈接】 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\n",getMinAns(n,n,r)); }return 0; }
BZOJ 2466 [中山市選2009]樹(高斯消元)