1. 程式人生 > >[JZOJ5952] 凱旋而歸 ([BZOJ 5092]【Lydsy1711月賽】分割序列)【高維字首和】【DP】

[JZOJ5952] 凱旋而歸 ([BZOJ 5092]【Lydsy1711月賽】分割序列)【高維字首和】【DP】

Description

對於一個長度為m的序列a,記 f ( a ) = max ( (

a 1   x o r   . . .
  x o r   a i ) + (
a i + 1   x o r   . . .   x o r   a n ) ) , i [ 0 , m ] f(a)=\max((a_1\ xor\ ...\ xor\ a_i)+(a_{i+1}\ xor\ ...\ xor\ a_n)),i\in[0,m]

給你一個長度為n的序列a,依次輸出 a [ 1..1 ] , a [ 1..2 ] , . . . , a [ 1.. n ] a[1..1],a[1..2],...,a[1..n] 的f值
a i 1 0 6 , n 300000 a_i\leq 10^6,n\leq 300000

Solution

我們先將序列a做一次字首異或,記 b i = a 1   x o r   . . .   x o r   a i b_i=a_1\ xor\ ...\ xor\ a_i
那麼 f ( a [ 1.. i ] ) = max ( b j + ( b i   x o r   b j ) ) , j [ 0 , i ] f(a[1..i])=\max(b_j+(b_i\ xor\ b_j)),j\in[0,i]

從高到低考慮 b i b_i 的二進位制位,如果 2 k 2^k 這一位為1,那麼j無論怎麼選這一位對總和貢獻都是 2 k 2^k
如果這一位為0,那麼就要看 b j b_j 的這一位,如果為1那麼就有 2 k + 1 2^{k+1} 的貢獻,否則貢獻就是0

問題就轉化成我們需要選擇一個j,使得 b j b_j b i b_i 為0的更的高位要儘可能為1,為1的位可以任意,然後按位確定答案。

問題就在於這個為1的位可以任意。我們很難用一個Trie來維護,因為碰到1需要兩邊都一起跑。
考慮預處理
預處理狀態 F [ S ] F[S] ,S為一個二進位制狀態,一開始S的意義就是S這個二進位制數在序列中最早出現的位置,直接掃一遍存進來即可。

為了滿足我們為1的位可以任意取,我們修改 F [ S ] F[S] 的定義, F [ S ] F[S] 表示所有二進位制數 T T 在序列中最早出現的位置, S   a n d   T = S S\ and\ T=S ,或者說 S T S\in T ,即S的某一位為0實際上T可以為0或1。
這個動態維護比較難,我們先掃一遍存進來,然後做DP,列舉每一個為0的位轉移。
本質上,這是一個高維字首和的過程。

現在查詢答案就好查了,我們從高位到低位掃 b i b_i ,前面已經確定好的不變,然後看這一位是0還是1,是0就判斷是否存在 F [ S ] i , F[S']\leq i, S’前面都滿足且這一位為1,然後直接統計即可。

(其實用Trie也是可以的,我們先離線將Trie建出來,然後1的兒子不變,0的兒子合併1的兒子狀態,本質與上面相同)

複雜度 O ( ( n + max ( a ) ) log max ( a ) ) O((n+\max(a))\log \max(a))

Code

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 460005
using namespace std;
int a[N],n,f[1048576],cf[21],ans[N];
int main()
{
	cin>>n;
	a[0]=0;
	cf[0]=1;
	fo(i,1,20) cf[i]=cf[i-1]<<1;
	memset(f,107,sizeof(f));
	fo(i,1,n) 
	{
		scanf("%d",&a[i]),a[i]^=a[i-1];
		f[a[i]]=min(f[a[i]],i);
	}
	fod(j,cf[20]-1,0) 
		fo(i,0,19) if(!(j&cf[i])) f[j]=min(f[j],f[j^cf[i]]);
	fo(i,1,n)
	{
		int s=0,v=0;
		fod(j,19,0)
		{
			if(a[i]&cf[j]) s+=cf[j];
			else
			{
				v^=cf[j];
				if(f[v]<=i) s+=cf[j+1];
				else v^=cf[j];
			}
		}
		printf("%d\n",s);
	}	
}