奇怪的姿势:高维前缀和
首先请先看一题:
Bzoj5092:
一句话题面:
我们做xor前缀和后,对每一个i,输出j<i,max(s[i]^s[j]+s[j])。
一般人估计上来就想可持久化Trie了。
然而我们发现,对于某一位,如果s[i]这位为1,对于s[j],这位如果是0的话贡献为1,是1的话贡献为0。
but,如果s[i]这位为0,那么对于s[j],这位如果是0的话贡献为0,是1的话贡献为2。
等等,这怎么还带进位的?这玩意能做?
可持久化Trie当然是做不了的,于是我们需要一种叫做高维前缀和的东西。
考虑我们如果把i的二进制表示看做一个集合(状态压缩总会吧),我们要统计i的超集的一些信息。
说人话,就是对于i,我们要满足统计(j&i)==i的j的一些信息。
这就是高维前缀和所做的事情了。
如何统计?
我们先从低到高满足j和i存在差异的第一个位置,然后对不包含这个位置的i补全这个位置,并统计答案。
也就是:
if( ! ( i & ( 1 << b ) ) ) f[i] = f[i] add f[i|(1<<b)]
这里的add可以是任何满足加和性质的运算,比如+,^,min(),max(),等等。
考虑这样做为什么是对的,首先和i在更低位置有差异的已经被统计了(废话,看你前面位数怎么循环的)。
然后考虑此时的f[j](这里j=i|(1<<b)),他包含了和j在更低位置存在差异的数值的信息。
也就是说,我们在把f[j]的信息加入f[i]的同时,同时加入了和i在这一位有差异且在更低位有差异的所有值的信息。
所以这个东西不会漏算。
为什么不会算重?因为我们统计的时候每次的最高差异位是不同的!
考虑如何用这个东西来完成Bzoj5092。
我们令f[k]表示二进制表示包含k的最靠前位置。
然后我们从高到低贪心枚举位数。
如果s[i]的当前位为1,我们对于s[j]此位选择0和1都相同,我们可以忽略这位。
如果s[i]当前位为0,我们就要贪心选择s[j]当前位为1的j。
假设我们已经选出的状态为cur。如果我们能够找到这样的j的话,需要满足的条件是:f[cur^(1<<b)]<=i。
去判定一下就可以了。
代码:
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 const int maxn=3e5+1e2,maxm=4e6+1e2,lim=2097152; 5 6 int in[maxn],f[maxm]; 7 8 inline void pre() { 9 for(int b=0;b<=20;b++) 10 for(int i=0;i<lim;i++) 11 if( ! ( i & ( 1 << b ) ) ) f[i] = std::min( f[i] , f[i|(1<<b)] ); 12 } 13 14 int main() { 15 static int n; 16 scanf("%d",&n) , memset(f,0x3f,sizeof(f)); 17 for(int i=1;i<=n;i++) { 18 scanf("%d",in+i) , 19 in[i] ^= in[i-1] , 20 f[in[i]] = std::min( f[in[i]] , i ); 21 } 22 pre(); 23 for(int i=1,cur;i<=n;i++) { 24 cur = 0; 25 for(int b=20;~b;b--) if( ! ( in[i] & ( 1 << b ) ) ) { 26 if( f[cur|(1<<b)] <= i ) cur |= ( 1 << b ); 27 } 28 printf("%d\n",(in[i]^cur)+cur); 29 } 30 return 0; 31 }