【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;
}
View Code

 

另一种比较慢的是折半搜索(二分),技巧性比较强,空间换时间。

用二进制记录状态,枚举前半数点决策得到每个状态的最少按钮数。

枚举后半数点觉得得到的每个状态与契合状态(当前状态+契合状态=111111)的最少按钮数相加得到答案。

实质是通过记录状态,分段枚举,大大节约了时间。

posted @ 2017-08-31 20:45  ONION_CYC  阅读(479)  评论(0编辑  收藏  举报