1. 程式人生 > >bzoj 5092 [Lydsy1711月賽]分割序列——高維字首和

bzoj 5092 [Lydsy1711月賽]分割序列——高維字首和

題目:https://www.lydsy.com/JudgeOnline/problem.php?id=5092

套路地弄一個字首異或和,就變成 f[ i ]=max_{j=0}^{i} { s[ j ] + (s[ i ]^s[ j ]) }。再套路地考慮按位貪心。

然後看了題解。按位貪心不是確定 f[ i ] 的這一位是0還是1,而是確定這一位是否給答案貢獻 bin[ j ] !

按位考慮,自己這一位如果是1,則 j 不管取在哪,都只有一種情況,就是向答案貢獻 bin[ j ];

  自己這一位如果是0,則 j 的位置影響到這一位對答案的貢獻是 0 還是 2*bin[ j ] 。

因為自己這一位是0了,又要有那樣的貢獻,所以 j 的這一位一定是1;做到這一位的時候已經知道更高的位上選擇了哪些位為1,所以如果知道自己位置是否可以在滿足更高的選了1的位仍舊選1的基礎上把這一位也選上1的話就好了。

這裡有很好的思路:不是求自己能否達到,而是預處理整個數組裡符合這個條件的最靠前的位置!這樣自然也能判斷自己是否可行了。

即,預處理滿足更高位確定、當前位為1、更低位隨便的a[ ]的最靠前位置。

這個可以用高維字首和來做。初值是 p[ a[ i ] ]=min{ p[ a[ i ] ] , i } ,倒著列舉 i 就不用取min了;沒有出現的值的 p 是 n+1 ,p[ 0 ] = 0。

狀態裡的 0 表示不作要求,1表示必須為1;則從低到高位列舉,p[ i ]對(最高位到當前位固定、當前位到最低位是自己子集)的p[ j ]取min即可。

計算答案的時候注意因為 a[ i ]&bin[ j ] 而產生貢獻的,不用算在和 p 有關的那個限制裡。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=3e5+5,M=25,K=(1<<20)+5;//K!=1e6!!!
int n,a[N],bin[M],p[K];
int rdn()
{
  int ret=0;bool fx=1;char ch=getchar();
  while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
  
while(ch>='0'&&ch<='9') ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar(); return fx?ret:-ret; } int Mx(int a,int b){return a>b?a:b;} int Mn(int a,int b){return a<b?a:b;} int main() { int mx=0,lm; n=rdn();for(int i=1;i<=n;i++)a[i]=rdn()^a[i-1],mx=Mx(mx,a[i]); bin[0]=1; for(lm=1;(bin[lm-1]<<1)<=mx;lm++) bin[lm]=bin[lm-1]<<1; bin[lm]=bin[lm-1]<<1; for(int i=1;i<bin[lm];i++)p[i]=n+1; for(int i=n;i;i--)p[a[i]]=i; p[0]=0; for(int t=0;t<lm;t++) for(int i=1;i<bin[lm];i++) if(!(i&bin[t])) p[i]=Mn(p[i],p[i|bin[t]]); for(int i=1,ans=0,lj=0;i<=n;i++,ans=0,lj=0) { for(int j=lm-1;j>=0;j--) { if(a[i]&bin[j]) {ans+=bin[j];continue;}//no lj|=bin[j] if(p[lj|bin[j]]<=i) { ans+=bin[j]<<1;lj|=bin[j]; } } printf("%d\n",ans); } return 0; }