1. 程式人生 > >[CSAcademy]Or Problem

[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;
}