區間異或和的和


計蒜客 - 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\n",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\n",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\n",res);
        }
    }
    return 0;
}
咕咕咕


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM