【JZOJ3834】【NOI2015模拟9.14】【CF461D】Complicated Task(异或方程组+并查集)

Problem

这里写图片描述

Input

这里写图片描述

Output

这里写图片描述

Hint

对于30%的数据,N<=15
对于100%的数据,N,K<=10^5

Solution

  若设点(i,j)的颜色为c[i][j](’x’为0,’o’为1),对于棋盘中的每个点(i,j),都有异或方程c[i-1][j]^c[i][j-1]^c[i][j+1]^c[i+1][j]=0。
  那么考虑对于每个点(i+1,j),c[i+1][j]就等于c[i-1][j]^c[i][j-1]^c[i][j+1]。
  这样可以做个DP,再在外面套个暴力,枚举第一行的状态。不过只有30分。
  那么我们套路一波,推一推第一行的点对棋盘中每个点的影响。
  比如说,对于c[i][j],它等于c[i-2][j]^c[i-1][j-1]^c[i-1][j+1],而c[i-1][j-1]又等于c[i-3][j-1]^c[i-2][j-2]^c[i-2][j],c[i-1][j+1]也可以用类似这样的方法表示,那么c[i-2][j]会被算3次,消掉2次,再拿它往上带。这样不断地回代,我们可以发现,c[i][j]的取值取决于第一行连续的一段列数为奇数或为偶数的点的颜色,具体地说,就是从点(i,j)向上画两条射线,以x轴正方向为0°,右边一条为45°,左边一条为135°,这两条射线投影在第一行上。
  如果我们把棋盘重标号为[0..N-1],那么我们可以通过找规律得知,对于一个点(x,y),它在第0行上的投影的左端点l=abs(a-b),右端点r=2*(n-1)-a-b。
  这样的话,我们就可以求出对于那K个给定的点,它在第0行上的投影。通俗地说,设第0行上的点的颜色为a[0],a[1],…,a[N-1],则对于某个给定的点(x,y),c[x][y]可以表示为a[l]^a[l+2]^a[l+4]^…^a[r]。
  这样我们就得到了K个异或方程。但请别一看见方程就想着高斯消元,别忘了数据范围:K<=10^5。
  然而此题并不需要我们求出一种方案,所以我们可以另辟蹊径解决这个难题。
  我们可以假设有异或前缀和s[i],表示a[0/1]^a[2/3]^a[4/5]^…^a[i-2]。这么写是因为我们这个前缀和还需要分奇偶。譬如s[5],5是个奇数,所以s[5]=a[1]^a[3]。
  那么,上述方程均可表示为s[l]^s[r+2]的形式。
  这样的话,如何判断其可否有解呢?
  我们可以把每个前缀和拆开成两个点,0和1,分别表示此前缀和为0和为1的情况。对于K个点中的某点,若其颜色为0,则s[l]与s[r+2]的值相同,所以从s[l][0]向s[r+2][0]连一条边,s[l][1]向s[r+2][1]连一条边;若其颜色为1,则s[l]与s[r+2]的值不同,所以从s[l][0]向s[r+2][1]连一条边,s[l][1]向s[r+2][0]连一条边。
  对于连边,我们可以用并查集。如果有某次连边构成了环,则必然有某个x,它的s[x][0]和s[x][1]之间有一条边,那么意思就是s[x][0]的值和s[x][1]的值相同,0和1相同,显然不合法。于是直接输出方案数0,return。
  如果没有的话,我们可以求出所有的联通块个数。显然s[0][0]、s[0][1]、s[1][0]、s[1][1]这4个点的颜色都是已知的,所以它们所在的联通块中的所有点的颜色也均为已知,所以将联通块个数-4;而又因为我们得出的联通块必然是对称的,那假如有两条边从s[2][0]连向s[4][1],从s[2][1]连向s[4][0],我们又不可能既将s[2]定为0、s[4]定为1,又将s[2]定为1、s[4]定为0,所以减完4还要再/2。
  然后我们就得出未知的、本质不同的联通块个数cnt,也即高斯消元中的自由元个数了。我们对于其中一个联通块,都可以给其赋2个值,于是答案即 2cnt 2 c n t
  时间复杂度: O(n+kα(n)) O ( n + k α ( n ) )

Code

#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
#define N 200100
#define M 1000000007
#define fo(i,a,b) for(i=a;i<=b;i++)
int i,n,k,x,y,l,r,fat[N],cnt;
char ch[1];
int gef(int x)
{
    return fat[x]?fat[x]=gef(fat[x]):x;
}
inline bool uni(int x,int y)
{
    int fx=gef(x),fy=gef(y);
    if(gef(fx^1)==fy||gef(fy^1)==fx)return 0;
    if(fx!=fy)fat[fx]=fy;
    return 1;
}
inline bool check(int l,int r,int p)
{
    return p?uni(2*l,2*r+1)&&uni(2*l+1,2*r):uni(2*l,2*r)&&uni(2*l+1,2*r+1);
}
int pow2(int x)
{
    int ans=1;
    fo(i,1,x)ans=ans*2%M;
    return ans;
}
int main()
{
    scanf("%d%d",&n,&k);
    fo(i,1,k)
    {
        scanf("%d%d%s",&x,&y,&ch);
        x--;
        y--;
        l=abs(x-y);
        r=min(x+y,2*(n-1)-x-y);
        if(!check(l,r+2,ch[0]=='o'))
        {
            printf("0\n");
            return 0;
        }
    }
    fo(i,0,2*n+3)cnt+=(gef(i)==i);
    printf("%d",pow2((cnt-4)/2));
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值