zoukankan      html  css  js  c++  java
  • 区间异或和的和

    计蒜客 - A1613 UVALive - 8518  

    Sum of xor sum

    这两题是一样的,但后面那个数据为空的,你输出“下次一定”都能过

     我们要求的就是给你任意区间[L,R],能得出这么一个东西

    ,直接从数的本身下手,是没有想法的。异或这个位操作有关的,我们可以从二进制位来考虑,如果我们知道了每一位对答案的贡献,那么最后直接把所有位的答案再加起来即可。

    这里我们对j位进行讨论,用sum[i-1][j]来表示,前i-1个数在二进制位置j的答案前缀和,当在加多一个数a[i],怎么得到sum[i][j]呢?

    首先,当加多a[i]之后,多的区间自然是[1,i],[2,i]...[3,i]以i为右边界的区间,这时我们再看哪些区间对答案有贡献。

    用xr[i][j]表示前i个数在j位置的异或和,那对答案有贡献的区间情况无非两种,设区间为[k,i],便是xr[k][j]=0,xr[i][j]=1跟xr[k][j]=1,xr[i][j]=1。

    因为xr[k][j]跟xr[i][j]相同的话,不就表示着j位在(k,i]区间内有偶数个1,这偶数个1异或为0,那(k,i]也就是[k+1,i]区间对答案自然没有贡献。

    由此我们可知,当xr[i][j]=1时,对答案有贡献的区间左端点k便是xr[k][j]=0,而xr[i][j]=0时则相反。

    所以这时候我们要用cnt0[i][j],来记录前i个数在j位置的异或和为0的数有多少个,cnt1[i][j]同理。明显初始值,cnt0[0][j]=1,cnt1[0][j]=0。

    那么sum[i][j]=sum[i-1][j]+2j*(xr[i][j] ? cnt0[i-1][j] : cnt1[i-1][j]); (2j为j为的权重),也就是在[0,i-1]中找一个左端点k,使得(k,i]中有奇数个1。

    要求区间[L,R]内的答案,自然是对每一位求ans+=sum[R]-sum[L-1],j位区间右端点在[L,R]范围中的答案

    但此时还多算了点答案,也就是左端点在[0,L-1],而右端点在[L,R]中的答案,所以还得把这部分减去

    ans-=2j*cnt0[L-2][j]*(cnt1[R][j]-cnt1[L-1][j]),ans-=2j*cnt1[L-2][j]*(cnt0[R][j]-cnt0[L-1][j])

    为什么是L-2呢,看在[0,i-1]中找一个左端点k,使得(k,i]中有奇数个1那里,我们的区间是左开右闭的。

    如果是L-1的话,就变成了(L-1,L~R]=[L,L~R]多减去了左端点为L的情况。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e5+11,M=21,md=1000000007;
    int cf2[N],sum[N][M],xr[N][M],cnt[3][N][M];
    int main(){
        int t,n,q,x;
        cf2[0]=1;
        for(int i=0;i<M;i++){
            if(i) cf2[i]=cf2[i-1]<<1;
            cnt[0][0][i]=1;
            cnt[1][0][i]=0;
        }
        scanf("%d",&t);
        while(t--){
            scanf("%d%d",&n,&q);
            for(int i=1;i<=n;i++){
                scanf("%d",&x);
                for(int j=0;j<M;j++){
                    int pw=(x>>j)&1;
                    pw=xr[i][j]=xr[i-1][j]^pw;
                    cnt[0][i][j]=cnt[0][i-1][j]+(pw^1);
                    cnt[1][i][j]=cnt[1][i-1][j]+pw;
                    sum[i][j]=sum[i-1][j]+1ll*cf2[j]*cnt[pw^1][i-1][j]%md;
                    if(sum[i][j]>=md) sum[i][j]-=md;
                }
            }
            int l,r;
            while(q--){
                scanf("%d%d",&l,&r);
                long long ans=0;
                for(int i=0;i<M;i++){
                    ans+=sum[r][i]-sum[l-1][i];
                    ans=(ans%md+md)%md;
                    if(l>=2){
                        ans-=1ll*cf2[i]*cnt[0][l-2][i]%md*(cnt[1][r][i]-cnt[1][l-1][i])%md;
                        ans=(ans%md+md)%md;
                        ans-=1ll*cf2[i]*cnt[1][l-2][i]%md*(cnt[0][r][i]-cnt[0][l-1][i])%md;
                        ans=(ans%md+md)%md;
                    }
                }
                printf("%lld
    ",ans);
            }
        } 
        return 0;
    }
    下次一定

    那很明显,上面的做法并不支持修改,所以要是还有单点修改的话,我们就得用线段树来处理。(见的世面太小,区间修改的题还没见过

    我们要维护哪些东西呢,len为这个区间的长度,sum[j]为这个区间内j位的答案,lj[j]是以这个区间的左部来作为区间的左端点使得区间内有奇数个1的右端点个数

    而rj[j]自然是以这个区间的右部来作为区间的右端点使得区间内有奇数个1的左端点个数,cnt[j]就是记录这个区间内有几个1。

    其他地方就跟正常线段树差不多,主要就是补充说明一下向上更新时,两个区间的合并。

    Tree merge(Tree &ll,Tree &rr){
        Tree ans;
        ans.l=ll.l;ans.r=rr.r; 
        for(int i=0;i<M;i++){
            ans.sum[i]=ll.sum[i]+rr.sum[i];
            ans.sum[i]+=ll.rj[i]*(rr.len-rr.lj[i]);
            ans.sum[i]+=rr.lj[i]*(ll.len-ll.rj[i]);
         if(ll.cnt[i]&1) ans.lj[i]=ll.lj[i]+(rr.len-rr.lj[i]); else ans.lj[i]=ll.lj[i]+rr.lj[i]; if(rr.cnt[i]&1) ans.rj[i]=rr.rj[i]+(ll.len-ll.rj[i]); else ans.rj[i]=rr.rj[i]+ll.rj[i]; ans.cnt[i]=ll.cnt[i]+rr.cnt[i]; ans.len=ll.len+rr.len; } return ans; }

    直接用代码进行说明(这里当然也可以不传引用直接传值,但传引用会快一点),假设要合并的是ll,rr区间,其中ll区间合并时是在左边的,而rr是右边的,而合并后的区间是ans

    除了之前两区间的答案之和,合并之后就得多考虑左端点在ll中,右端点在rr中的区间对答案的贡献,也就是考虑左边奇数个1,右边偶数个1还有反过来的情况。

    代码也就是ans.sum[i]+=ll.rj[i]*(rr.len-rr.lj[i]) ans.sum[i]+=rr.lj[i]*(ll.len-ll.rj[i]);

    因为rr是接在ll的右边,所以ans的lj[i],就得看ll的1的数目来决定。而ans的rj[j]同理即可,这里就对lj[i]进行说明。

    如果ll总共有奇数个1,那么跟rr中应该以左部为左端点的区间就应该要求有偶数个1,也就是拿区间的长度减去奇数个1的情况来得到。

    代码就是if(ll.cnt[i]&1) ans.lj[i]=ll.lj[i]+(rr.len-rr.lj[i]); //左边奇数个1,右边以左部为左端点的区间就应该有偶数个1

    else ans.lj[i]=ll.lj[i]+rr.lj[i];//相反右边以左部为左端点的区间就应该有奇数个1

    #include<bits/stdc++.h>
    #define L(x) (x<<1)
    #define R(x) (x<<1|1)
    #define M(x) ((T[x].l+T[x].r)>>1)
    using namespace std;
    const int N=1e5+11,M=20,md=1000000007;
    typedef long long ll;
    struct Tree{
        int l,r,len;
        int sum[M],lj[M],rj[M],cnt[M];
        Tree(){
            for(int i=0;i<M;i++)
                sum[i]=lj[i]=rj[i]=cnt[i]=0;
        }
    }T[N<<2];
    int a[N];
    Tree merge(Tree &ll,Tree &rr){
        Tree ans;
        ans.l=ll.l;ans.r=rr.r; 
        for(int i=0;i<M;i++){
            ans.sum[i]=ll.sum[i]+rr.sum[i];
            ans.sum[i]+=1ll*ll.rj[i]*(rr.len-rr.lj[i])%md;
            if(ans.sum[i]>=md) ans.sum[i]-=md;
            ans.sum[i]+=1ll*rr.lj[i]*(ll.len-ll.rj[i])%md;
            if(ans.sum[i]>=md) ans.sum[i]-=md;
            if(ll.cnt[i]&1) ans.lj[i]=ll.lj[i]+(rr.len-rr.lj[i]);
            else ans.lj[i]=ll.lj[i]+rr.lj[i];
            if(rr.cnt[i]&1) ans.rj[i]=rr.rj[i]+(ll.len-ll.rj[i]);
            else ans.rj[i]=rr.rj[i]+ll.rj[i];
            ans.cnt[i]=ll.cnt[i]+rr.cnt[i];
            ans.len=ll.len+rr.len;
        }
        return ans;
    }
    void build(int x,int l,int r){
        T[x].l=l;T[x].r=r;
        if(l==r){
            T[x].len=1;
            for(int i=0;i<M;i++){
                T[x].sum[i]=T[x].lj[i]=T[x].rj[i]=T[x].cnt[i]=((a[l]>>i)&1);
            }
            return ;
        }
        build(L(x),l,M(x));
        build(R(x),M(x)+1,r);
        T[x]=merge(T[L(x)],T[R(x)]);
        return ;
    }
    Tree query(int x,int l,int r){
        if(l<=T[x].l&&r>=T[x].r) return T[x];
        if(r<=M(x)) return query(L(x),l,r);
        else if(l>M(x)) return query(R(x),l,r);
        else{
            Tree ll=query(L(x),l,r),rr=query(R(x),l,r);
            return merge(ll,rr);
        }
    }
    int main(){
        int t,n,q;
        scanf("%d",&t);
        while(t--){
            scanf("%d%d",&n,&q);
            for(int i=1;i<=n;i++) scanf("%d",&a[i]);
            build(1,1,n);
            int l,r,res;
            while(q--){
                scanf("%d%d",&l,&r);
                Tree ans=query(1,l,r);
                res=0;
                for(int i=M-1;i>=0;i--) res=(2ll*res%md+ans.sum[i])%md;
                printf("%lld
    ",res);
            }
        }
        return 0;
    } 
    区间合并

     牛客异或Tree

    lt是一个三维生物,他掌管着二维世界的运行规律,某一天他发现一颗有nnn个节点的无根树,该树只有点权,没有边权,现在他要进行mmm次操作,每次进行以下两种操作之一:

    1.选择一个节点,将其点权进行修改。

    2.给出参数u,v,询问u->v的简单路径上的所有点按顺序组成一个数组,计算这个数组的牛逼值。

    牛逼值就是区间异或和的和。

    这题涉及到树链剖分,不会的,可以右上角点击关闭了。

    虽然设有树链剖分的专栏,但一直没更,而且之前图论的博客也很粗糙,有空得进行更新,下次一定。

    (大胆妖孽,我一眼就看出你是一只鸽子精。)

    这题的话,无非就是的先用树链剖分处理一下,然后线段树中要增加个单点修改。也就是两部分的模板套起来即可。

    但需要注意的就是在树链剖分的树链的查询,答案的合并问题。

    我们要求u到v中的答案,可以看成u->lca,和lca->v这两条链(两个区间)合并的答案。

    而在树链上,我们每次查一段区间内的答案,所得到的答案的方向是红线方向。

    所以对于u->lca这条链上的答案就得把方向反过来之后再合并,而反过来其实就是lj[i]跟rj[i]进行互换即可。

    这需要分别保存两题链上的答案,最后再把它们合并。

    那么对于每次在u->lca链上,每新增加一部分答案,就得把那个答案方向反过来,再作为右边的区间与原先的合并。

    而对于lca->v链上的就是,新答案作为左区间跟原先的合并。

    #include<bits/stdc++.h>
    #define L(x) (x<<1)
    #define R(x) (x<<1|1)
    #define M(x) ((T[x].l+T[x].r)>>1)
    using namespace std;
    typedef long long ll;
    const int N=5e4+11,M=31;
    typedef long long ll;
    struct Tree{
        int l,r,len;
        int sum[M],lj[M],rj[M],cnt[M];
        Tree(){
            l=r=len=0;
            for(int i=0;i<M;i++) sum[i]=lj[i]=rj[i]=cnt[i]=0;
        } 
    }T[N<<2];
    int a[N],b[N];
    Tree merge(Tree &ll,Tree &rr){
        Tree ans;
        ans.l=ll.l;ans.r=rr.r; 
        for(int i=0;i<M;i++){
            ans.sum[i]=ll.sum[i]+rr.sum[i];
            ans.sum[i]+=ll.rj[i]*(rr.len-rr.lj[i]);
            ans.sum[i]+=rr.lj[i]*(ll.len-ll.rj[i]);
            if(ll.cnt[i]&1) ans.lj[i]=ll.lj[i]+(rr.len-rr.lj[i]);
            else ans.lj[i]=ll.lj[i]+rr.lj[i];
            if(rr.cnt[i]&1) ans.rj[i]=rr.rj[i]+(ll.len-ll.rj[i]);
            else ans.rj[i]=rr.rj[i]+ll.rj[i];
            ans.cnt[i]=ll.cnt[i]+rr.cnt[i];
            
        }
        ans.len=ll.len+rr.len;
        return ans;
    }
    void build(int x,int l,int r){
        T[x].l=l;T[x].r=r;
        if(l==r){
            T[x].len=1;
            for(int i=0;i<M;i++){
                T[x].sum[i]=T[x].lj[i]=T[x].rj[i]=T[x].cnt[i]=((b[l]>>i)&1);
            }
            return ;
        }
        build(L(x),l,M(x));
        build(R(x),M(x)+1,r);
        T[x]=merge(T[L(x)],T[R(x)]);
        return ;
    }
    void updata(int x,int pos,int val){
        if(T[x].l==pos&&T[x].r==pos){
            for(int i=0;i<M;i++){
                T[x].sum[i]=T[x].lj[i]=T[x].rj[i]=T[x].cnt[i]=((val>>i)&1);
            }
            return ;
        }
        if(pos<=M(x)) updata(L(x),pos,val);
        else updata(R(x),pos,val);
        T[x]=merge(T[L(x)],T[R(x)]);
        return ;
    }
    Tree query(int x,int l,int r){
        if(l<=T[x].l&&r>=T[x].r) return T[x];
        if(r<=M(x)) return query(L(x),l,r);
        else if(l>M(x)) return query(R(x),l,r);
        else{
            Tree ll=query(L(x),l,r),rr=query(R(x),l,r);
            return merge(ll,rr);
        }
    }
    //上面为线段树维护区间异或和的和 
    struct Side{
        int v,ne;
    }S[N<<1];
    char op[18];
    int sn,head[N],tn,tid[N],size[N],dep[N];
    int top[N],fa[N],son[N];
    void initS(int n){
        sn=tn=0;
        for(int i=0;i<=n;i++){
            fa[i]=son[i]=0;
            size[i]=1;
            dep[i]=1;
            head[i]=-1;
        }
    }
    void addS(int u,int v){
        S[sn].v=v;
        S[sn].ne=head[u];
        head[u]=sn++;
    }
    void dfs1(int u){
        for(int i=head[u];~i;i=S[i].ne){
            int v=S[i].v;
            if(v==fa[u]) continue;
            fa[v]=u;
            dep[v]=dep[u]+1;
            dfs1(v);
            size[u]+=size[v];
            if(size[v]>=size[son[u]]) son[u]=v;
        }
    }
    void dfs2(int u,int tf){
        tid[u]=++tn;
        b[tn]=a[u];
        top[u]=tf;
        if(!son[u]) return ;
        dfs2(son[u],tf);
        for(int i=head[u];i!=-1;i=S[i].ne){
            int v=S[i].v;
            if(v!=fa[u]&&v!=son[u]) dfs2(v,v);
        }
    }
    Tree querypath(int x,int y){
        int flag=1; 
        Tree ans1,ans2,temp;
        while(top[x]!=top[y]){
            if(dep[top[x]]<dep[top[y]]){
                flag^=1;
                swap(x,y);
            }
            temp=query(1,tid[top[x]],tid[x]);
            if(flag){
                for(int i=0;i<M;i++) swap(temp.lj[i],temp.rj[i]);
                ans1=merge(ans1,temp);
            }else ans2=merge(temp,ans2);
            x=fa[top[x]];
        }
        flag^=1;
        if(dep[x]>dep[y]){
            swap(x,y);
            flag^=1;
        }
        temp=query(1,tid[x],tid[y]);
        if(flag){
            for(int i=0;i<M;i++) swap(temp.lj[i],temp.rj[i]);
            ans1=merge(ans1,temp);
        }else ans2=merge(temp,ans2);
        ans1=merge(ans1,ans2);
        return ans1;
    }
    int main(){
        int t,n,m,op,u,v;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        initS(n);
        for(int i=1;i<n;i++){
            scanf("%d%d",&u,&v);
            addS(u,v);
            addS(v,u);
        }
        dfs1(1);
        dfs2(1,1);
        build(1,1,n);
        while(m--){
            scanf("%d%d%d",&op,&u,&v);
            if(op==1) updata(1,tid[u],v);
            else{
                Tree ans=querypath(u,v);
                ll res=0;
                for(int i=M-1;i>=0;i--) res=((res<<1ll)+ans.sum[i]);
                printf("%lld
    ",res);
            }
        }
        return 0;
    }
    咕咕咕

  • 相关阅读:
    NSIS 2.0界面快速入门
    [反汇编练习] 160个CrackMe之016
    [反汇编练习] 160个CrackMe之015
    [反汇编练习] 160个CrackMe之014
    [反汇编练习] 160个CrackMe之013
    Ubuntu 安装之python开发
    git 技能图
    linux 系统 权限
    Git 常用命令
    python 之virtualenv和virtualenvwrapper使用
  • 原文地址:https://www.cnblogs.com/LMCC1108/p/12964902.html
Copyright © 2011-2022 走看看