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

本文介绍了一种基于动态规划解决车辆装载问题的方法,通过逐步优化原始算法的时间复杂度从O(n²k)降低到O(nlognlogw),并讨论了如何使用单调队列和wqs二分法提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值