决策单调性优化DP 学习笔记 & P4767 [IOI2000] 邮局 题解

0. 题面

题目描述

高速公路旁边有一些村庄。高速公路表示为整数轴,每个村庄的位置用单个整数坐标标识。没有两个在同样地方的村庄。两个位置之间的距离是其整数坐标差的绝对值。

邮局将建在一些,但不一定是所有的村庄中。为了建立邮局,应选择他们建造的位置,使每个村庄与其最近的邮局之间的距离总和最小。

你要编写一个程序,已知村庄的位置和邮局的数量,计算每个村庄和最近的邮局之间所有距离的最小可能的总和。

输入格式

第一行包含两个整数:第一个是村庄 V 的数量,第二个是邮局的数量 P

第二行包含 V 个整数。这些整数是村庄的位置。

输出格式

第一行包含一个整数S,它是每个村庄与其最近的邮局之间的所有距离的总和。

样例 #1

样例输入 #1

10 5 
1 2 3 6 7 9 11 22 44 50

样例输出 #1

9

提示

对于 40% 的数据,V300

对于 100% 的数据,1P300PV30001 村庄位置 10000

1. 朴素算法

容易看出这是一道 DP 题。不妨令 ai 为位置第 i 大的村庄的位置(约定a0=aV+1=),fi,j 为修建 i 个邮局且最后一个邮局在 aj 处时前 j 个村庄与其最近的邮局之间的所有距离的总和,w(l,r) 为在 alar 处修建邮局后在 alar 之间的所有村庄与其最近的邮局之间的所有距离的总和。不难得出以下递推式:

fi,j={mink=1j1{fi1,k+w(k,j)}2iPw(0,j)i=1

答案即为 ans=minj=1Vfp,j+w(j,V+1)

事实上,使用前缀和技术,w 函数可以在 Θ(1) 的时间复杂度内计算。约定 mid=al+ar2sumi 为在位置 i 及其左侧所有村庄的位置值之和,cnti 为在位置 i 及其左侧所有村庄的数量,可以知道

w(l,r)=i=lrmin{aial ,arai }=il  aimid(aial)+ir  ai>mid(arai)// mid  al mid  ar=il  aimidaiil  aimidal+ir  ai>midarir  ai>midai=(summidsumal1)al(cntmidcntal1)+ar(cntarcntmid)(sumarsummid)

于是我们得到了一个时间复杂度为 Θ(VP2) 的算法。但是它太慢了,需要优化。

2. 决策单调性优化

fi,j=fi1,pi,j+w(k,pi,j) ,我们称 pi,jfi,j 的“最优决策点”。可以知道,递推式 f 具有“决策单调性”,即对于每个 ij,均有 pi,jpi,j+1。证明如下:

w(a,c)+w(b,d)w(a,d)+w(b,c) (abcd)ffi,jfi,j+1f

因此可以发现,如果求得 pi,则可以知道 pjpi(j<i)pjpi(j>i) ,这样我们枚举法求 p 时就可以排除许多无用状态。因此可以想到一个分治算法,代码如下:

void dfs(int l,int r,int ll,int rr){
    //l,r: f下标范围
    //ll,rr: p可能的范围
    if(l>r)return;
    int mid=(l+r)/2,p=0;
    f[mid]=INF;
    for(int i=ll;i<mid;i++){
        if(f[mid]>g[i]+w(a[i],a[mid])){
            f[mid]=g[i]+w(a[i],a[mid]);
            p=i;
        }
    }//枚举求
    dfs(l,mid-1,ll,p);
    dfs(mid+1,r,p,rr);
}

不难发现这个算法的时间复杂度是 Θ(VPlogV) ,足以通过此题。完整代码如下:

#include <iostream>
#include <algorithm>
using namespace std;
const int MAXP=300,MAXV=3000,MAXX=10000,INF=0x3f3f3f3f;
int v,p,a[MAXV+5],cnt[MAXX+5],sum[MAXX+5],pre[MAXX+5],suf[MAXX+5],R,f[MAXV+5],g[MAXV+5];
int w(int l,int r){
    int mid1=(l+r)/2;
    return sum[mid1]-sum[l-1]-l*(cnt[mid1]-cnt[l-1])+r*(cnt[r]-cnt[mid1])-(sum[r]-sum[mid1]);
}
void dfs(int l,int r,int ll,int rr){
    if(l>r)return;
    int mid=(l+r)/2,k=0;
    f[mid]=INF;
    for(int i=ll;i<mid;i++){
        if(f[mid]>g[i]+w(a[i],a[mid])){
            f[mid]=g[i]+w(a[i],a[mid]);
            k=i;
        }
    }
    dfs(l,mid-1,ll,k);
    dfs(mid+1,r,k,rr);
}
int main(){
    ios::sync_with_stdio(false);
    cin>>v>>p;
    for(int i=1;i<=v;i++){
        cin>>a[i];
        cnt[a[i]]++;
        sum[a[i]]+=a[i];
    }
    sort(a+1,a+1+v);
    R=a[v];
    for(int i=1;i<=R;i++){
        cnt[i]+=cnt[i-1];
        sum[i]+=sum[i-1];
    }
    for(int i=1;i<=R;i++){
        pre[i]=i*cnt[i]-sum[i];
        suf[i]=sum[R]-sum[i-1]-i*(cnt[R]-cnt[i-1]);
    }
    for(int i=1;i<=v;i++){
        f[i]=pre[a[i]];
    }
    int ans=INF;
    for(int i=2;i<=p;i++){
        swap(f,g);
        dfs(1,v,1,v);
    }
    for(int i=1;i<=v;i++){
        ans=min(ans,f[i]+suf[a[i]]);
    }
    cout<<ans<<endl;
    return 0;
}
posted @   ztx-  阅读(44)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
点击右上角即可分享
微信分享提示