[FJWC2017]生日礼物-TopCoder Member SRM 494 Div 1 KnightsOut

【问题描述】

给出一个 NM 的方格棋盘,每个格子里有一盏灯和一个开关,开始的时候,所有的灯都是关着的。用 (x,y) 表示第 x 行,y 列的格子。 (x,y) 的开关可以改变 (x,y) 中灯的状态,同时也可以改变满足 |xx|=2,|yy|=1 或者 |xx|=1,|yy|=2 的格子 (x,y) 的状态。改变状态的意思是,原来开着的灯会被关掉,原来关着的灯会被开起来。注意这边的改变状态是强制改变的。每个格子的开关最多只能按一次,求能使得所有灯都打开的方案数 mod 123456789 的值。

【数据规模与约定】

对于 20% 的数据,有 1n×m20
对于 40% 的数据,有 1n×m200
对于 70% 的数据,有 1n,m150
对于 100% 的数据,有 1n,m600


【题解】

显然我们可以看出,开关的处理就是异或嘛,然后就可以对于每一个节点按不按开关设成一个变量,然后暴力列方程高斯消元就可以了。这样复杂度的话是 O((nm)3) 的,即使使用 bitset 优化也是过不了的啊。
既然无法减少计算的复杂度,就看能不能削减方程数量。


我们知道,对于下面这样一个情况

3517x2846
如果我们可以确定图上 X,1,2,3,4,5,6,7 节点的取法,那么我们就可以确定 8 的取值。那么,由于我们是从上到下从左到右枚举的,那么对于每一个作为 8 的点,就可以确定 X,1,2,3,4,5,6,7 ,然后唯一确定这个点。
具体地,我们设每一个点的左数一格,上数两格作为其的 X 格子,那么我们就能确定这个点了。
显然,有一些节点是不存在 X 格子的(如下图中的 ! 点),那么这些节点就需要作为变量。
!!!!!!!!!!!!!!!!
然后我们就这样用变量来往下表示,一直到底下一些节点。
我们实际上在做什么呢?对于每一个 8 号节点,我们相当于都是已经使用掉了这个节点的方程。因此若是有一些节点,这些节点无法作为别的节点的 X 格子,那么这些节点的方程就没有被用掉,我们就只能将其列出来解掉。如下图,这些 ? 节点的方程是没有被用掉的,得列出来。
????????????????
于是我们就把这些方程列出来,高斯消元一下,求下自由元就好了,最后答案就是 2

代码如下

#include <bits/stdc++.h>
#define R register
#define LL long long
template<class TT>inline TT Max(R TT a,R TT b){return a<b?b:a;}
template<class TT>inline TT Min(R TT a,R TT b){return a<b?a:b;}
using namespace std;
template<class TT>inline void read(R TT &x){
    x=0;R bool f=false;R char c=getchar();
    for(;c<48||c>57;c=getchar())f|=(c=='-');
    for(;c>47&&c<58;c=getchar())x=(x<<1)+(x<<3)+(c^48);
    (f)&&(x=-x);
}
const int dx[]={-1,-2,-2,-1, 1, 2, 2, 1};
const int dy[]={-2,-1, 1, 2, 2, 1,-1,-2};
const int mod = 123456789;
inline LL qpow(R LL a,R LL b){
    R LL r=1;
    while(b){
        if(b&1)r=r*a%mod;
        b>>=1;
        a=a*a%mod;
    }
    return r%mod;
}

const int maxn = 610;
const int maxm = maxn*3;
typedef bitset<maxm> BbB;
BbB a[maxn][maxn],tmp;
vector<BbB> S;
int n,m,ans;

inline bool in(R int x,R int y){
    return -1<x&&x<n&&-1<y&&y<m;
}
inline void pre_work(){
    for(R int i=0,lim=Min(2,n);i<lim;++i){
        for(R int j=0;j<m;++j){
            a[i][j][i*m+j]=1;
        }
    }
    for(R int i=2;i<n;++i)a[i][0][(m<<1)+i-2]=1;

    for(R int i=2;i<n;++i){
        for(R int j=1,x,y;j<m;++j){
            x=i-2;y=j-1;
            a[i][j]=a[x][y];
            for(R int k=0,xx,yy;k<8;++k){
                xx=x+dx[k];
                yy=y+dy[k];
                if(in(xx,yy)&&(xx!=i||yy!=j))a[i][j]^=a[xx][yy];
            }
        }
    }
    for(R int i=n-3;i>=0;i--){
        tmp=a[i][m-1];
        for(R int j=0,x,y;j<8;++j){
            x=i+dx[j];
            y=m-1+dy[j];
            if(in(x,y))tmp^=a[x][y];
        }
        S.push_back(tmp);
    }
    for(R int i=Max(n-2,0);i<n;++i){
        for(R int j=0;j<m;++j){
            tmp=a[i][j];
            for(R int k=0,x,y;k<8;++k){
                x=i+dx[k];y=j+dy[k];
                if(in(x,y))tmp^=a[x][y];
            }
            S.push_back(tmp);
        }
    }
}
inline void get_ans(){
    R int lim=S.size(),cnt=0;
    for(R int i=0,j;i<lim;++i){
        for(j=cnt;j<lim&&!S[j][i];++j);
        if(j==lim)ans++;
        else{
            swap(S[cnt++],S[j]);
            for(j=cnt;j<lim;++j){
                if(S[j][i])S[j]^=S[cnt-1];
            }
        }
    }
}
int main(){
    read(n);read(m);
    pre_work();
    get_ans();
    printf("%lld\n",qpow(2,ans)%mod);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值