【BZOJ】1770 [Usaco2009 Nov]lights 燈
【算法】高斯消元-异或方程组
【题解】良心简中题意
首先开关顺序没有意义。
然后就是每个点选或不选使得最后得到全部灯开启。
也就是我们需要一种确定的方案,这种方案使每盏灯都是开启的。
异或中1可以完美实现取反。
故令xi表示第i盏灯的开关情况,然后对每盏灯的亮灭列方程,即
(1*x1)^(1*x2)^(0*x3)=1 该方程表示第1、2盏灯和该灯相邻(或就是该灯)
就这样n个方程对应n盏灯的亮灭。
题目不保证唯一解,所以可能存在自由元(即多解)。
之后就从n到1进行DFS,确定一个算一个。DFS中可以用最优性剪枝。
注意:虽然没有回代过程,a[x][n+1]的值仍然会改变得不一样,所以在dfs中每次要借用这个值必须用t来代之修改。不然动到原值,之后引用就会出错。
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int maxn=600,inf=0x3f3f3f3f; int tot=0,n,a[maxn][maxn],anss,ans,A[maxn],m; void gauss(){ for(int i=1;i<=n;i++){ int r=0; for(int j=i;j<=n;j++)if(a[j][i]){r=j;break;} if(r==0)continue; if(r!=i)for(int j=1;j<=n+1;j++)swap(a[i][j],a[r][j]); for(int k=i+1;k<=n;k++)if(a[k][i]) for(int j=i;j<=n+1;j++)a[k][j]^=a[i][j]; } } void dfs(int x){ if(anss>=ans)return; if(x==0){ans=anss;return;} if(a[x][x]){ int t=a[x][n+1]; for(int i=x+1;i<=n;i++)t^=a[x][i]*A[i]; A[x]=t; if(t)anss++; dfs(x-1); if(t)anss--; } else{ A[x]=0;dfs(x-1); A[x]=1;anss++;dfs(x-1);anss--; } } int main(){ scanf("%d%d",&n,&m); int u,v; for(int i=1;i<=m;i++){ scanf("%d%d",&u,&v); a[u][v]=a[v][u]=1; } for(int i=1;i<=n;i++)a[i][i]=a[i][n+1]=1; gauss(); ans=inf;anss=0; dfs(n); printf("%d",ans); return 0; }
另一种比较慢的是折半搜索(二分),技巧性比较强,空间换时间。
用二进制记录状态,枚举前半数点决策得到每个状态的最少按钮数。
枚举后半数点觉得得到的每个状态与契合状态(当前状态+契合状态=111111)的最少按钮数相加得到答案。
实质是通过记录状态,分段枚举,大大节约了时间。