P9580 「Cfz Round 1」Wqs Game 题解

· · 题解

更好的阅读体验

挺好的博弈论题,这是一个跟官方题解不太一样的做法。

遇到这种组合游戏可以先考虑逆推胜负,把握一下规律,我们先从一个区间的胜负判断开始入手。

考察区间中最后一个数字的从属关系,如果它属于弈,因为 a_i>0,如果前面传来的数字非空,则弈不用选择也可以获胜,否则只要选择当前的数即可。

如果它属于博,那么这个数得刚好为 0 或者等于 a_i,博才有办法获胜,以此推广到最后有多个数属于博的情况:只有前面传来的数能被这些数异或表示(在它们的张成空间中),博才能获胜。

而这个规律推广到更前面的连续段也是适用的,即对于数列的任意一个前缀中属于弈的数的张成空间,都是对应后缀属于博的数的张成空间的子集时,博才能获胜。

找到了规律,我们接着来设计算法进行统计,一个维护这种“区间中的子区间”数量的常用方法是对一个端点进行扫描线,维护 S 数组表示以对应位置为开头或结尾,且以当前扫描到的这个数为另一个端点时,合法的区间数。

由于刚才我们是从右往左推理,我们也动态从右往左扫描,对于一个以弈拥有的数为右端点的区间贡献肯定为 r-l+1,而另一种区间如果在扫描中遇到了一个节点,且它不在当前这个张成空间中,后面无论再怎么扫也一定无解,前面一定有解,也就是说这种胜负具有一定单调性。

这启发我们先预处理出每个弈拥有的点在右端点到哪才能被右边属于博的数异或表示出,记为 rpos_i 可以倒着扫描整个序列,贪心维护一个线性基,每次加数时淘汰时间比较老的节点,当加到 a_{i+1} 时就可以处理 a_i 的信息,对它用到的节点的时间戳取最大即可。

回到刚才的扫描线,由于“当前没有机会的右端点集合”只要一开始把所有都加进去就是只增不减,每次扫描到一个属于弈的数就把 i 到 rpos_i 中的所有数标记为合法,可以使用并查集或者 set 维护,之后开个树状数组维护答案,这里数点的方式有很多种,这里不再赘述。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<set>
#include<vector> 
#define N 500005
#define M 1500006
#define ll long long
using namespace std;
typedef unsigned long long ul;
typedef unsigned int ui;
ui ans;
ui Ans;
ul Sd,Cnt;
ul cnt,n,q,tp,a[N];
ul Rd(){Sd^=Sd<<19,Sd^=Sd>>12,Sd^=Sd<<29;return Sd^=++Cnt;}
char s[N];
void GetA(ul &a){a=Rd()%((1ll<<60)-2)+1;}
void GetLR(ul &l,ul &r){
    l=Rd()%n+1,r=Rd()%n+1;
    if(l>r)swap(l,r);
}
ll read(){
    ll x=0;char ch=getchar();
    while(ch<'0' || ch>'9')ch=getchar();
    while(ch>='0' && ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x;
}
//-----------------------------------------------
struct que{
    ul l,r;
}Q[M];
void init(){
    n=read();q=read();tp=read();
    char ch=getchar();
    while(ch<'0' || ch>'1')ch=getchar();
    while(ch>='0' && ch<='1'){s[++cnt]=ch;ch=getchar();}
    if(tp){
        Sd=tp,Cnt=0;
        for(ll i=1;i<=n;++i)GetA(a[i]);
        for(ll qi=1;qi<=q;++qi){GetLR(Q[qi].l,Q[qi].r);}
    }
    else{
        for(ll i=1;i<=n;i++)a[i]=read();
        for(ll i=1;i<=q;i++){Q[i].l=read();Q[i].r=read();}
    }
}
ll rpos[N];
vector<que> P[N];
ll bas[65],ti[65];
void insert(ll x,ll t){
    for(ll i=61;i>=0;i--){
        if(x&(1ll<<i)){
            if(!bas[i]){bas[i]=x;ti[i]=t;return;}
            else if(t<ti[i]){swap(x,bas[i]);swap(t,ti[i]);x^=bas[i];}
            else x^=bas[i];
        }
    }
}
ll jud(ll x){
    ll ans=0;
    for(ll i=61;i>=0;i--){
        if(x&(1ll<<i)){
            x^=bas[i];ans=max(ans,ti[i]);
        }
    }
    if(x) return n+1;
    else return ans;
}
int ask_b(ll x,ll t){
    ll ans=0;
    for(ll i=61;i>=0;i--){
        if(x&(1ll<<i) && t<=ti[i]){
            x^=bas[i];ans=max(ans,ti[i]);
        }
        else if(x&(1ll<<i))return false;
    }
}
set<ll> S;
ll F[N],G[N];
ll lowbit(ll x){
    return x&(-x);
}
void add(ll x,ll val,ll *K){
    while(x<=n){
        K[x]+=val;
        x+=lowbit(x);
    }
}
ll ask(ll x,ll *K){
    ll ans=0;
    while(x){
        ans+=K[x];
        x-=lowbit(x);
    }
    return ans;
}
ui an[N];
int main(){
    init();ll pos=1;
    rpos[n]=n+1;
    for(ll i=n;i>=1;i--){
        if(s[i]=='0')insert(a[i],i);
        else rpos[i]=jud(a[i]);
    }
    for(ll i=1;i<=n;i++)if(s[i]=='0'){S.insert(i);add(i,i,F);add(i,1,G);}
    for(ll i=1;i<=q;i++)P[Q[i].l].push_back((que){i,Q[i].r});
    for(ll i=n;i>=1;i--){
        if(s[i]=='1'){
            set<ll>::iterator itt=S.lower_bound(i);
            set<ll>::iterator it=itt;
            for(;itt!=S.end() && *itt<rpos[i];itt++){
                add(*itt,-*itt,F);add(*itt,-1,G);
                add(*itt,*itt-i,F);
            }
            S.erase(it,itt);
        }
        for(ll j=0;j<P[i].size();j++){
            ll r=P[i][j].r;
            ans=0;ans=ask(r,F)-ask(i-1,F)-(ask(r,G)-ask(i-1,G))*(i-1);
            ans=(ll)(r-i+2)*(r-i+1)/2-ans;
            if(!tp)an[P[i][j].l]=(ans%(1ll<<32));
            ans%=(1ll<<32);
            ans=(ans*P[i][j].l)%(1ll<<32);
            ui tmp=ans;
            Ans^=tmp;
        }
    }
    if(tp)cout<<Ans;
    else for(ll i=1;i<=q;i++)cout<<an[i]<<endl;
}