[CSAcademy]Or Problem
[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;
}