解题思路:刚好暑假学完了线段树,我一开始就把这道题当做线段树的裸题,用线段树来维护数据,不就是单点更新,区段查询嘛。但是很不幸时间超限,后来我考虑了原来每次查询进化石的时候还是要全部遍历一遍,所以是要超时的。这时我们就需要更高效的方法来存储和查询所有进化石的状态,这种方法就是利用二进制进行状态压缩。
首先我们要明确一点加入的是第几种进化石,而不是几块进化石,也就是看的是存不存在,而不是存在的数量!!
我们用二进制数的每一位来表示每种进化石的有无,也即该小镇进化石的存在状态。例如,对于有 1、4 号进化石的小镇,我们可以用二进制数 1001 来表示。它的含义是:从右向左依次表示第 1 个到第 n 个进化石的有无,1 表示有,0 表示无。而且很容易想到,由于每一位只有 0 或 1 两种可能,且每一位都对应固定的编号,所以对于任意一个二进制数,都能保证唯一对应一种存在状态。
解决了如何表示存在状态的问题,下一步就是如何存储了。例如,当前我们的进化石存在状态为:1、4,对应二进制 1001,如果我们加入一个 3 号进化石,则应变为 1101,也就是让倒数第三位变成 1。这里需要用到位运算:对于 1001,我们让它与 0100(只含有 3 号石的状态)进行或运算,即两数对应的位有一个或两个为 1 时结果为1,否则为 0,运算结果为 1101。这样我们使用或运算就可以实现两个状态的合并。至于如何表示单个进化石的状态,很简单,使用左移运算就可以了,例如:表示 3 号石存在,只需将 1(0001)左移 3-1=2 位即得到 0100。
这样,我们只需要把二进制和线段树结合一下就可以愉快地告别 TLE 了。在存储时,每一个结点都表示它的左右子结点的合并状态,即对左右子结点进行或运算后的结果,而叶结点直接存储状态。在查询时,只需要遍历结果对应二进制的每一位来输出即可。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define ll long long int 5 const int MAXN=1e5+10; 6 using namespace std; 7 int n,q; 8 ll sum[MAXN<<2]; 9 void push_up(int i)///向上回溯,状态合并 10 { 11 sum[i]=sum[i<<1]|sum[i<<1|1]; 12 } 13 void build(int l,int r,int rt)///建树 14 { 15 if(l==r) 16 { 17 sum[rt]=0; 18 return ; 19 } 20 int mid=(r+l)>>1; 21 build(l,mid,rt<<1); 22 build(mid+1,r,rt<<1|1); 23 push_up(rt); 24 } 25 void add(int l,int r,int pos,int v,int rt) 26 { 27 if(l==r) 28 { 29 sum[rt]|=1ll<<(v-1);///1ll为长整型的1 30 return ; 31 } 32 int m=(r+l)>>1; 33 if(m>=pos) 34 { 35 add(l,m,pos,v,rt<<1); 36 } 37 else 38 { 39 add(m+1,r,pos,v,rt<<1|1); 40 } 41 push_up(rt); 42 } 43 void del(int l,int r,int pos,int val,int rt) 44 { 45 if(l==r) 46 { 47 sum[rt]&=~(1ll<<(val-1)); 48 return ; 49 } 50 int mid=(r+l)>>1; 51 if(pos<=mid) 52 { 53 del(l,mid,pos,val,rt<<1); 54 } 55 else 56 { 57 del(mid+1,r,pos,val,rt<<1|1); 58 } 59 push_up(rt); 60 } 61 ll query(int l,int r,int L,int R,int rt)///区间查询 62 { 63 if(L<=l&&R>=r) 64 { 65 return sum[rt]; 66 } 67 ll ans=0; 68 int mid=(r+l)>>1; 69 if(L<=mid) 70 { 71 ans|=query(l,mid,L,R,rt<<1); 72 } 73 if(R>mid) 74 { 75 ans|=query(mid+1,r,L,R,rt<<1|1); 76 } 77 return ans; 78 } 79 80 int main() 81 { 82 int i,t; 83 int op,x,y; 84 scanf("%d",&t); 85 for(i=1; i<=t; i++) 86 { 87 printf("Case %d:\n",i); 88 scanf("%d%d",&n,&q); 89 build(1,n,1); 90 while(q--) 91 { 92 scanf("%d",&op); 93 scanf("%d%d",&x,&y); 94 if(op==1) 95 { 96 add(1,n,x,y,1); 97 } 98 if(op==2) 99 { 100 del(1,n,x,y,1); 101 } 102 if(op==3) 103 { 104 ll ans=query(1,n,x,y,1); 105 ll cnt=0; 106 int flag=1; 107 int ot=1; 108 while(ans) 109 { 110 if(ans&1) 111 { 112 if(flag) 113 { 114 flag=0; 115 } 116 else 117 { 118 printf(" "); 119 } 120 printf("%d",ot); 121 } 122 ot++; 123 ans>>=1; 124 } 125 if(flag) 126 { 127 printf("%%\n"); 128 } 129 else 130 { 131 printf("\n"); 132 } 133 } 134 } 135 } 136 return 0; 137 }