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; }