暑假集训中期测试 Problem G: 维护序列 (线段树)
Description
现在有一个N个整数组成的序列,这N个整数的标号分别为1, 2, ..., N,对这个序列一共进行两类操作:
① 1 x y:表示将第x个和第y个(包括x、y)整数之间的所有整数的二进制的最低位的1变为0,如果某个整数的值为0,则不对这个整数做任何改变。
② 2 x y :表示你需要回答第x个和第y个(包括x、y)整数之间的所有整数异或的结果。
Input
输入包含多组测试数据。
对于每组测试数据,第一行包含两个正整数N(2<=N<=10^4)、M(1<=M<=10^5),表示这个序列一共有N个整 数,你需要处理M次操作。接下来一行一共有N个不超过2^30的非负整数,依次描述了这个序列中各个整数的初始值。再接下来一共有M行,每行均描述了一种 操作。
Output
对于每个第②类操作,用一行输出一个整数表示回答的结果。
Sample Input
3 4
3 6 0
2 2 2
2 1 2
1 1 3
2 1 3
Sample Output
6
5
6
分析:一开始总在想这题中对区间的操作如何更新区间的关键信息(如果一个一个更新显然会超时),硬是没想出个名堂。后来看了标程才明白,原来对区间的更新就是一个一个进行的,只不过每段区间的更新次数是有一个上限的(29),所以,在每个区间保存一个关键信息(区间中所有元素是否均为0)指示该区间是否需要更新就可以避免许多不必要的更新,从而降低时间复杂度。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdio.h> #define N 10001 int n,m; int a[N]; int ans[4*N]; bool f[4*N]; void update(int cur) { int ls=cur<<1,rs=cur<<1|1; ans[cur]=ans[ls]^ans[rs]; f[cur]=f[ls]|f[rs]; } void build(int cur,int x,int y) { int mid=x+y>>1,ls=cur<<1,rs=cur<<1|1; if(x==y) { f[cur]=ans[cur]=a[x]; return; } build(ls,x,mid); build(rs,mid+1,y); update(cur); } void change(int cur,int x,int y,int s,int t) { int mid=x+y>>1,ls=cur<<1,rs=cur<<1|1; if(!f[cur]) return; if(x==y) { ans[cur]^=ans[cur]&(-ans[cur]); f[cur]=ans[cur]; return; } if(mid>=s) change(ls,x,mid,s,t); if(mid+1<=t) change(rs,mid+1,y,s,t); update(cur); } void query(int cur,int x,int y,int s,int t,int &ret) { int mid=x+y>>1,ls=cur<<1,rs=cur<<1|1; if(x>=s && y<=t) { ret^=ans[cur]; return; } if(mid>=s) query(ls,x,mid,s,t,ret); if(mid+1<=t) query(rs,mid+1,y,s,t,ret); } void solve(int opt,int x,int y) { int ret=0; if(x>y) {int tmp=x;x=y;y=tmp;} if(opt==1) change(1,1,n,x,y); else query(1,1,n,x,y,ret),printf("%d\n",ret); } int main() { int opt,x,y,i; while(~scanf("%d%d",&n,&m)) { for(i=1;i<=n;i++) scanf("%d",&a[i]); build(1,1,n); while(m--) { scanf("%d%d%d",&opt,&x,&y); solve(opt,x,y); } } return 0; }