[CSAcademy]Or Problem
阿新 • • 發佈:2018-12-19
[CSAcademy]Or Problem
題目大意:
一個長度為\(n(n\le2\times10^5)\)的序列\(A(0\le A_i<2^{20})\),將其分為恰好\(m\)個連續段,設每一段的代價為這一段數字的或,總代價為每一段代價和。求最小代價和。
思路:
一個普通的DP思路是,對於每個數\(A_i\),列舉每一位,找到上一個在這一位上為\(1\)的數\(A_k\),\(A_{k+1\sim i}\)為最後一段。轉移方程為\(f[i][j]=\max\{f[k][j-1]+\vee_{\ell=k+1}^i A_{\ell}\}\)。
使用帶權二分可以去掉\(m\)段的狀態。
原始碼:
#include<cstdio> #include<cctype> #include<cstring> #include<algorithm> inline int getint() { register char ch; while(!isdigit(ch=getchar())); register int x=ch^'0'; while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0'); return x; } typedef long long int64; const int N=2e5+1,logN=18,logA=20; int n,st[N][logN],p[logA],g[N]; int64 f[N]; inline int lg2(const float &x) { return ((unsigned&)x>>23&255)-127; } inline int calc(const int &l,const int &r) { const int k=lg2(r-l+1); return st[l][k]|st[r-(1<<k)+1][k]; } inline void solve(const int &c) { memset(p,0,sizeof p); for(register int i=1;i<=n;i++) { f[i]=calc(1,i)-c; g[i]=0; for(register int j=0;j<logA;j++) { if(st[i][0]>>j&1) p[j]=i; if(!p[j]) continue; const int64 tmp=f[p[j]-1]+calc(p[j],i)-c; if(f[i]<tmp) { f[i]=tmp; g[i]=0; } if(tmp==f[i]) g[i]=std::max(g[i],g[p[j]-1]+1); } } } int main() { n=getint(); const int m=getint(); for(register int i=1;i<=n;i++) st[i][0]=getint(); for(register int j=1;j<logN;j++) { for(register int i=1;i+(1<<(j-1))<=n;i++) { st[i][j]=st[i][j-1]|st[i+(1<<(j-1))][j-1]; } } int l=0,r=1e9; int64 ans=0; while(l<=r) { const int mid=(l+r)>>1; solve(mid); if(g[n]>=m) { l=mid+1; ans=f[n]+1ll*m*mid; } else { r=mid-1; } } printf("%lld\n",ans); return 0; }