bzoj 5092 [Lydsy1711月賽]分割序列——高維字首和
阿新 • • 發佈:2018-11-28
題目: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; }