[JZOJ5373]信仰是为了虚无之人

题目大意

现在你要构造一个长度为n,字典序最小的数组a。
给定m个限定要求,每个要求形如(l,r,x),可能合法也可能不合法。表示要数组满足a[l]^…a[r]=x,如果满足之前的合法要求之后无法满足这个要求,则这个要求不合法,否则合法。强制在线,每次读入一个要求后,合法输出1,否则输出0。
n<=200000, m<=400000

分析

这其实远古时代做过…比赛的时候不会做,吃了记忆力不好的亏。
先不考虑怎么让a字典序最小,先考虑怎么判断要求是否合法。
很关键的一点是把要求化成前缀xor和的形式,设sum[i]=sum[i-1]^a[i],则限制变为sum[r]^sum[l-1]==x。这样相当于把区间限制变成了两点限制。那么一个合法限制相当于在两个点a,b间连一条边,边权为x,代表sum[a]^sum[b]==x。然后不合法的限制就是和之前形成的a到b的路径xor和矛盾。
一个经典做法是带权并查集,这是他的一个应用。考虑到xor的优美性质,我们可以路径压缩。对于a,b在同一个并查集的情况,是不用再连边的,简单地判断并查集路径xor和是否满足限制即可;对于不同并查集,把父亲连起来即可,边权计算一下可以弄出来,相当于原来一些路径的xor和。(就不用连a,b了)。

代码

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<set>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
typedef long long ll;
typedef double db;
const int N=2e5+5;
int val[N],fa[N],sum[N],l,r,z,i,n,m,s,lst,pp,x,y,pd[N];
int get(int x)
{
    if (fa[x]==x) return x;
    int ret=get(fa[x]);
    val[x]^=val[fa[x]];
    return fa[x]=ret;
}
int main()
{
    freopen("t1.in","r",stdin);
    freopen("t1.out","w",stdout);
    scanf("%d %d %d",&n,&m,&s);
    fo(i,0,n) fa[i]=i;
    fo(i,1,m)
    {
        scanf("%d %d %d",&l,&r,&z);
        l^=s*lst;r^=s*lst;z^=s*lst;
        x=get(l-1);
        y=get(r);
        pp=1;
        if (x!=y)
        {
            val[x]=val[l-1]^val[r]^z;
            fa[x]=y;
        }else if ((val[r]^val[l-1])!=z)  pp=0;
        lst=pp;
        printf("%d\n",pp);
    }
    fo(i,0,n)
    if (!pd[i])
    {
        x=get(i);
        if (!pd[x])
        {
            sum[i]=sum[i-1];
            sum[x]=sum[i]^val[i];
            pd[x]=1;
        }else
        {
            sum[i]=sum[x]^val[i];
        }
        pd[i]=1;
    }
    fo(i,1,n) printf("%d\n",sum[i]^sum[i-1]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值