专栏文章
题解:P10009 [集训队互测 2022] 线段树
P10009题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @mina7640
- 此快照首次捕获于
- 2025/12/01 23:05 3 个月前
- 此快照最后确认于
- 2025/12/01 23:05 3 个月前
题意
给定一个长度为 的序列。有 次操作,每次操作形如区间替换为异或差分或者单点求值。最后还要输出整个序列。
,3s。
题解
稍微思考一下可以发现这个不太线段树可维护,因为前面的东西可能会很大程度上影响到后面的东西。但是有一个比较好的性质是 次修改后影响的元素最多距离是 。由此可以考虑定期重构:对操作每 个一组处理,序列每 个分一块,则每一组操作的块只有块内和相邻的块会有影响。
首先考虑如何处理整块的 tag。稍作分析可以得到贡献是组合数,因为异或只需要考虑奇偶性,因此 次整体做差分后, 会变成 的异或和,其中 在二进制下是 的子集。换言之,为了重构,只需对于 的每个非零位 ,每隔 个做一次差分即可。这样重构一个块的复杂度不超过 。
为了方便,每 次操作对于每个相邻的两块考虑,再遍历这 个操作。询问就直接枚举子集,而修改如果当前两块都是整块就增加 tag 的值,否则按照上述方法重构再暴力做。最后处理完所有的 次操作记得再重构一下。
分析一下复杂度。查询的复杂度是枚举子集,不超过 ,而每次涉及的散块重构复杂度是 ,而每组询问最后重构复杂度一共是 。取 ,总时间复杂度 。
代码
非常好写!
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;
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...