题解:P10009 [集训队互测 2022] 线段树

· · 题解

题意

给定一个长度为 n 的序列。有 q 次操作,每次操作形如区间替换为异或差分或者单点求值。最后还要输出整个序列。

## 题解 稍微思考一下可以发现这个不太线段树可维护,因为前面的东西可能会很大程度上影响到后面的东西。但是有一个比较好的性质是 $t$ 次修改后影响的元素最多距离是 $d$。由此可以考虑定期重构:对操作每 $B$ 个一组处理,序列每 $B$ 个分一块,则每一组操作的块只有块内和相邻的块会有影响。 首先考虑如何处理整块的 tag。稍作分析可以得到贡献是组合数,因为异或只需要考虑奇偶性,因此 $q$ 次整体做差分后,$a_i$ 会变成 $a_{i-c}$ 的异或和,其中 $c$ 在二进制下是 $q$ 的子集。换言之,为了重构,只需对于 $q$ 的每个非零位 $2^j$,每隔 $2^j$ 个做一次差分即可。这样重构一个块的复杂度不超过 $O(B\log q)$。 为了方便,每 $B$ 次操作对于每个相邻的两块考虑,再遍历这 $B$ 个操作。询问就直接枚举子集,而修改如果当前两块**都**是整块就增加 tag 的值,否则按照上述方法重构再暴力做。最后处理完所有的 $B$ 次操作记得再重构一下。 分析一下复杂度。查询的复杂度是枚举子集,不超过 $O(qB)$,而每次涉及的散块重构复杂度是 $O(qB\log q)$,而每组询问最后重构复杂度一共是 $O\left(\dfrac qBn\log q\right)$。取 $B=\Theta(\sqrt n)$,总时间复杂度 $O(n+q\sqrt n\log q)$。 ## 代码 非常好写! ```cpp #include <bits/stdc++.h> #define rep(i,n) for(int i=0,del##i##verme=int(n);i<del##i##verme;++i) using namespace std; int orn; const int B=512; int subid,n,q,a[252005],c,b[252005],res[252005]; int tp[252005],l[252005],r[252005],ans[252005]; void solve(int *arr,int L,int R,int x) { rep(i,20) if((x>>i)&1) { for(int j=R-(1<<i),k=R;j>=L;--j,--k) arr[k]^=arr[j]; } } int main() { ios_base::sync_with_stdio(false);cin.tie(0); cin>>subid; cin>>n>>q; rep(i,n) { cin>>a[i]; } orn=n; while(n%B)++n; rep(i,q) { cin>>tp[i]>>l[i]; if(tp[i]==1) { cin>>r[i];--r[i]; } else { --l[i]; } } rep(_,q/B+1) { for(int i=0;i<n/B;++i) { c=0; int lb=i*B,rb=(i+1)*B-1,lb0=max(0,lb-B); memcpy(b+lb0,a+lb0,sizeof(int)*(rb-lb0+1)); for(int j=_*B;j<(_+1)*B&&j<q;++j) { if(tp[j]==1) { if(l[j]<=lb0&&rb<=r[j]) { ++c; } else if(!(r[j]<lb0||l[j]>rb)) { solve(b,lb0,rb,c); c=0; for(int k=min(r[j],rb);k>=max(lb0+1,l[j]);--k) b[k]^=b[k-1]; } } else if(lb<=l[j]&&l[j]<=rb) { int idx=l[j],ret=0; for(int cur=65536;cur;) { cur=(cur-1)&c; if(idx-cur>=0) { ret^=b[idx-cur]; } } ans[j]=ret; } } solve(b,lb0,rb,c); memcpy(res+lb,b+lb,sizeof(int)*B); } memcpy(a,res,sizeof(int)*n); } rep(i,q) if(tp[i]==2) cout<<ans[i]<<'\n'; rep(i,orn) cout<<a[i]<<'\n'; return 0; } ```