决策单调性优化DP 学习笔记 & P4767 [IOI2000] 邮局 题解
0. 题面
题目描述
高速公路旁边有一些村庄。高速公路表示为整数轴,每个村庄的位置用单个整数坐标标识。没有两个在同样地方的村庄。两个位置之间的距离是其整数坐标差的绝对值。
邮局将建在一些,但不一定是所有的村庄中。为了建立邮局,应选择他们建造的位置,使每个村庄与其最近的邮局之间的距离总和最小。
你要编写一个程序,已知村庄的位置和邮局的数量,计算每个村庄和最近的邮局之间所有距离的最小可能的总和。
输入格式
第一行包含两个整数:第一个是村庄 的数量,第二个是邮局的数量 。
第二行包含 个整数。这些整数是村庄的位置。
输出格式
第一行包含一个整数,它是每个村庄与其最近的邮局之间的所有距离的总和。
样例 #1
样例输入 #1
10 5
1 2 3 6 7 9 11 22 44 50
样例输出 #1
9
提示
对于 的数据,。
对于 的数据,,, 村庄位置 。
1. 朴素算法
容易看出这是一道 DP 题。不妨令 为位置第 大的村庄的位置(约定,), 为修建 个邮局且最后一个邮局在 处时前 个村庄与其最近的邮局之间的所有距离的总和, 为在 和 处修建邮局后在 和 之间的所有村庄与其最近的邮局之间的所有距离的总和。不难得出以下递推式:
答案即为
事实上,使用前缀和技术, 函数可以在 的时间复杂度内计算。约定 , 为在位置 及其左侧所有村庄的位置值之和, 为在位置 及其左侧所有村庄的数量,可以知道
于是我们得到了一个时间复杂度为 的算法。但是它太慢了,需要优化。
2. 决策单调性优化
设 ,我们称 为 的“最优决策点”。可以知道,递推式 具有“决策单调性”,即对于每个 ,均有 。证明如下:
因此可以发现,如果求得 ,则可以知道 , ,这样我们枚举法求 时就可以排除许多无用状态。因此可以想到一个分治算法,代码如下:
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);
}
不难发现这个算法的时间复杂度是 ,足以通过此题。完整代码如下:
#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;
}
本文作者:ztx-,使用署名-非商业性使用 4.0 国际进行许可
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)