【泉州一中国庆集训day4】破解

题目链接http://v.qzyz.com/contest/291/problem/1
题目大意:有一个长度为n的01串,初始时每个位置都是0,给定m个区间[li,ri],每次从中选择一个区间,将区间里的位置全部取反,操作可以进行任意次,求这个01串有多少种可能,输出答案模(10^9+7)。输入共T组数据。
数据范围:n<=10 000 000,m<=100 000,1<=li<=ri<=n,T<=10

题解:考虑原序列a的差分序列b,这里b[i]=a[i]^a[i-1],即a[i]=b[1]^b[2]^…^b[i],对一个区间[li,ri]取反时,可以通过对b[li]和b[ri+1]取反来实现,于是我们可以知道b中只有端点位置(li和ri+1)会等于1,其余位置都是0。
我们可以把问题转换成图论模型,对于一个区间[li,ri],在li和ri+1之间连一条无向边。于是问题就变成了:在一张图中给每条边赋值0/1,每个点的权值为与它相连的边的权值的异或和。这样我们可以独立计算每个连通块的答案。

设f(i)表示大小为 i 的连通块的答案,那么f(i)=2^(i-1)。
证明:显然f(1)=1=2^(1-1)。假设当前有一个大小为 i 的连通块,当新增加一条块内的边(u,v)时,若(u,v)=0则不影响结果,若(u,v)=1则必定可以找到一条u到v的路径将上面的边全部取反,同样不影响结果。当新增一条块内的点u与块外的点v的连边(u,v)时,若(u,v)=0则b[v]=0,其余的与原有方案一样;若(u,v)=1则必定在原有方案的基础上改变点u和点v的状态。所以此时方案数*2,即f(i+1)=f(i)*2。证毕。
所以我们只需要做一次图遍历,答案等于2^((每个连通块的点数-1)之和)

代码如下:

#include <algorithm>
#include <cstdio>
using namespace std;
const int mo=1000000007;
int a[200005],to[200005],ne[200005],fi[10000005],u[10000005],
    d[200005],i,n,m,T,x,y,t,tot,cnt,ans;
void add(int x,int y)
{
    to[++tot]=y;ne[tot]=fi[x];fi[x]=tot;
}
void bfs(int x)
{
    int i,s=1,t=1;
    for (u[d[1]=x]=1;s<=t;++s)
        for (i=fi[d[s]];i;i=ne[i])
            if (!u[to[i]]) u[d[++t]=to[i]]=1;
    cnt+=t-1;
}
int main()
{
    for (scanf("%d\n",&T);T--;tot=0)
    {
        scanf("%d%d\n",&n,&m);t=cnt=0;
        for (i=1;i<=m;++i)
        {
            scanf("%d%d\n",&x,&y);++y;
            a[++t]=x;a[++t]=y;
            add(x,y);add(y,x);
        }
        for (i=1;i<=t;++i)
            if (!u[a[i]]) bfs(a[i]);
        ans=1;
        for (i=1;i<=cnt;++i) ans=(ans<<1)%mo;
        printf("%d\n",ans);
        for (i=1;i<=t;++i) fi[a[i]]=u[a[i]]=0;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值