bzoj5311 贞鱼(dp+决策单调性+wqs二分)

首先我们有朴素dp
f [ i ] [ k ] 表示把前i个分成k段的最小代价
f [ i ] [ k ] = min j = 0 i 1 { f [ j ] [ k 1 ] + w ( j + 1 , i ) }
其中 w ( i , j ) 表示把i~j的贞鱼分在一辆车会产生的代价,可以预处理前缀和得到。(把矩阵中i>j的位置置为0,这样每次求的就是一个若干行的前缀的和)
这样复杂度 O ( n 2 k )
我们可以按k分层转移,
我们考虑k1< k2,如果某个时刻k2比k1优了,那么k2将永远比k1优。因为
f [ k 1 ] + s [ i ] [ i ] s [ k 1 ] [ i ] > f [ k 2 ] + s [ i ] [ i ] s [ k 2 ] [ i ]
f [ k 2 ] f [ k 1 ] < s [ k 2 ] [ i ] s [ k 1 ] [ i ]
不等式右边的那个式子是随着i的增大而增大的。
然而这个式子不能斜率优化!因为他的右边还是不只跟i有关,在换了决策点之后,他不一定是单增的!
因此我们只好对于每个k1,k2去二分算一个k2优于k1的最早时间i。
然后可以用一个单调队列来维护这些决策点,保证k2优于k1的最早时间单增。
复杂度 O ( n l o g n k )
还是过不去!
我们发现f[n][k]随着k是下凸的。
因此我们可以wqs二分!
复杂度 O ( n l o g n l o g w )

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define N 4010
inline char gc(){
    static char buf[1<<16],*S,*T;
    if(S==T){T=(S=buf)+fread(buf,1,1<<16,stdin);if(T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0,f=1;char ch=gc();
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=gc();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=gc();
    return x*f;
}
int n,K,s[N][N],f[N],w[N],ans=0,q[N];
inline int calc(int j,int i){
    return f[j]+s[i][i]-s[j][i];
}
inline bool better(int k1,int k2,int i){
    int val1=calc(k1,i),val2=calc(k2,i);
    if(val2<val1) return 1;
    if(val1<val2) return 0;
    return w[k2]<=w[k1];
}
inline int bettert(int k1,int k2){
    int l=k2+1,r=n;
    while(l<=r){
        int mid=l+r>>1;
        if(better(k1,k2,mid)) r=mid-1;
        else l=mid+1;
    }return r+1;
}
inline int jud(int mid){
    int qh=1,qt=0;q[++qt]=0;
    for(int i=1;i<=n;++i){
        while(qh<qt&&better(q[qh],q[qh+1],i)) ++qh;
        f[i]=calc(q[qh],i)+mid,w[i]=w[q[qh]]+1;
        while(qh<qt&&bettert(q[qt-1],q[qt])>bettert(q[qt],i)) --qt;q[++qt]=i;
    }return w[n];
}
int main(){
//  freopen("a.in","r",stdin);
    n=read();K=read();
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j) s[i][j]=read();
    for(int i=1;i<=n;++i)
        for(int j=1;j<i;++j) s[i][j]=0;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j) s[i][j]=s[i][j-1]+s[i][j];
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j) s[i][j]=s[i-1][j]+s[i][j];
    int l=0,r=s[n][n];
    while(l<=r){
        int mid=l+r>>1;
        if(jud(mid)<=K) r=mid-1,ans=f[n]-K*mid;
        else l=mid+1;
    }printf("%d\n",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/icefox_zhx/article/details/80758499
今日推荐