5092: [Lydsy1711月赛]分割序列
Time Limit: 5 Sec Memory Limit: 256 MBSubmit: 219 Solved: 100
[Submit][Status][Discuss]
Description
对于一个长度为n的非负整数序列b_1,b_2,...,b_n,定义这个序列的能量为:f(b)=max{i=0,1,...,n}((b_1 xor b
_2 xor...xor b_i)+(b_{i+1} xor b_{i+2} xor...xor b_n))其中xor表示按位异或(XOR),给定一个长度为n的非
负整数序列a_1,a_2,...,a_n,请计算a的每个前缀的能量值。
Input
第一行包含一个正整数n(n<=300000),表示序列a的长度。
第二行包含n个非负整数a_1,a_2,...,a_n(0<=a_i<=10^6),依次表示a中每个元素的值。
Output
包含n行,每行一个整数,即a每个前缀的能量值。
Sample Input
5
1 2 3 4 5
1 2 3 4 5
Sample Output
1
3
6
10
9
3
6
10
9
我们设c[i]为a[1]^a[2]^...^a[i],那么其实题目要求的就是对于每一个i求一个0<= j <=i使得 c[j] + (c[i] ^ c[j]) 最大。
我们从高位到低位贪心,如果c[i]在某一位为0,那么我们如果可以找到一个c[j]在这一位为1是再好不过的了;如果c[i]在某一位是1的话,那么c[j]在这位不管是1还是0都对答案没有影响,所以我们可以忽略c[i]为1的那些位。
因为我们是贪心的选,高位如果选了1的话那么之后这个1一定是要在选的里面的,所以现在问题就变成了: 给你一个数now,问你是否存在一个0<= j <=i 使得 c[j] & now = now.
这显然是一个子集覆盖的问题,因为限制只有右界,所以我们只要让出现的位置尽量靠前就好了。
于是设f[S]表示子集里有S的数最早出现的位置在哪,直接转移就行了。
查询的时候从高位到低位贪心即可。
#include<bits/stdc++.h> #define ll long long using namespace std; const int maxn=300005; int n,a[maxn],MP[maxn*13],ci[35]; int main(){ ci[0]=1; for(int i=1;i<=21;i++) ci[i]=ci[i-1]<<1; scanf("%d",&n),memset(MP,0x3f,sizeof(MP)); for(int i=1;i<=n;i++) scanf("%d",a+i),a[i]^=a[i-1],MP[a[i]]=min(MP[a[i]],i); for(int i=ci[21]-1;i>=0;i--) for(int j=0;j<=20;j++) if(i&ci[j]) MP[i^ci[j]]=min(MP[i^ci[j]],MP[i]); for(int i=1,now;i<=n;i++){ now=0; for(int j=20;j>=0;j--) if(!(a[i]&ci[j])&&MP[now|ci[j]]<=i) now|=ci[j]; printf("%d ",now+(a[i]^now)); } return 0; }