首页 > 其他分享 >[IOI2000] 邮局

[IOI2000] 邮局

时间:2023-06-28 19:33:51浏览次数:38  
标签:ch leq IOI2000 邮局 int 基站 村庄

题目描述

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

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

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

对于 \(40\%\) 的数据,\(V \leq 300\)。

对于 \(100\%\) 的数据,\(1 \leq P \leq 300\),\(P \leq V \leq 3000\),$1 \leq $ 村庄位置 \(\leq 10000\)。

思路点拨

我们从40分做起,这很好像,是子序列提取的模型,我们很容易设出状态并且转移。我们令 \(f_{i,j}\) 表示目前到村庄 \(i\) ,建立的 \(j\) 个基站(\(j\) 是基站)的最小消耗。转移就是枚举上一个基站的位置。

\[f_{i,j}=\min_{mid=0}^{i-1} {f_{mid,j-1}+w_{mid+1,i-1}} \]

其中 \(w_{l,r}\) 表示 \(l-1\) 和 \(r+1\) 村庄是基站,那么在 \([l,r]\) 之间的村庄会产生多少消耗。这个东西暴力是 \(O(n^3)\) 的,但是我想到了两种时间复杂度更优秀的做法可以求出它。第一种就是二分法,对于一个区间 \([l,r]\) ,如果我们可以找到最小一个点 \(i\) ,是的 \(i\) 村庄到左边的基站的距离相较于 \(i\) 到右边的基站的距离更远,那么答案无非就是如下:

  • \([l,i-1]\) 走向左边的基站,其余走向右边的基站

这样有超时的风险。我们发现,对于同一个 \(l\) ,当 \(r\) 不断变大的时候,我们所说的 \(i\) 有单调性, 所以可以扫一遍就可以了。这个是利用了 \(w\) 的单调性。给出预处理代码:

n=read(),m=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
	sort(a+1,a+n+1);
	for(int l=2;l<n;l++){
		int id=l;
		for(int r=l;r<n;r++){
			while(a[id]-a[l-1]<=a[r+1]-a[id]) id++;
			//id肯定会到右边去 
			w[l][r]=(sum[id-1]-sum[l-1])-(id-l)*a[l-1]+(r-id+1)*a[r+1]-(sum[r]-sum[id-1]);
		}
	}

我们接下来就不好优化了。但是这个dp是二维的,求得是最小值,我们不妨对 \(w\) 数组打表看看是否满足四边形不等式和区间包含单调性,答案是满足的。具体证明应该十分复杂,但是在考场上一般使用打表法。我们可以利用它优化到 \(O(VP)\) 。代码实现简单,但是边界得要求比较严格,给出如下参考代码:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-') f=-f;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} 
	return x*f;
}
const int MAXN=3e3+10;
int n,m,a[MAXN],sum[MAXN];
int w[MAXN][MAXN];
int pos[MAXN][MAXN],f[MAXN][MAXN];
signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
	sort(a+1,a+n+1);
	for(int l=2;l<n;l++){
		int id=l;
		for(int r=l;r<n;r++){
			while(a[id]-a[l-1]<=a[r+1]-a[id]) id++;
			//id肯定会到右边去 
			w[l][r]=(sum[id-1]-sum[l-1])-(id-l)*a[l-1]+(r-id+1)*a[r+1]-(sum[r]-sum[id-1]);
		}
	}
	for(int l=1;l<=n;l++)
		w[l+1][l]=0;
	for(int i=0;i<MAXN;i++)
		for(int j=0;j<MAXN;j++)
			f[i][j]=1e9;
	for(int i=1;i<=n;i++){
		f[i][1]=a[i]*(i-1)-sum[i-1];
		pos[i][1]=1;
	}
	for(int j=2;j<=m;j++){
		f[j][j]=0;
		pos[j][j]=j;
		pos[n+1][j]=n;
		for(int i=n;i>j;i--){
			for(int k=pos[i][j-1];k<=pos[i+1][j];k++)
				if(f[i][j]>f[k][j-1]+w[k+1][i-1]){
					f[i][j]=f[k][j-1]+w[k+1][i-1];
					pos[i][j]=k;
				}
		}
	}
	int ans=1e9;
	for(int i=m;i<=n;i++)
		ans=min(ans,f[i][m]+sum[n]-sum[i]-(n-i)*a[i]);
	cout<<ans;
	return 0;
}

标签:ch,leq,IOI2000,邮局,int,基站,村庄
From: https://www.cnblogs.com/Diavolo/p/17512340.html

相关文章

  • 邮局--dp经典问题
    题目:http://poj.org/problem?id=1160题意: 一些村庄被建立在一条笔直的高速公路边上,我们用一条坐标轴来描述这条高速公路,每一个村庄的坐标都是整数,没有两个村庄坐标相同。两个村庄间的距离,定义为它们的坐标值差的绝对值。我们需要在一些村庄建立邮局——当然,并不是每一个村庄都......
  • 外汇天眼:高雄又现假亲友借钱诈骗,邮局及时通报成功阻汇款!
    过去几年诈骗犯罪案件急速增长,而且手法与套路愈来愈多元,令人防不胜防。与此同时,那些看似老套的假亲友、猜猜我是谁等伎俩依然存在,并且还是有许多民众因此上当。上个礼拜,高雄警方就与辖区内邮局合作阻止一位老妇人受骗。据了解,5/22三民区民族派出所接到邮局的通报电话,行员表示现场......
  • 宝塔邮局-并解决A纪录解析失败问题
    为什么一定要用这个邮局呢,只要是方便,在宝塔面板直接安装就行了。使用教程如下:https://www.bt.cn/bbs/thread-87496-1-1.html有一个BUG本来已经设置好了,但是总显示A记录......
  • [IOI2000]邮局 题解
    简要题意线段上有\(V\)个村庄,现在要建\(P\)个邮局,使每个村庄到最近的邮局的距离之和最小。50分做法设\(dp[i][j]\)表示第一个村庄到第\(i\)个村庄,建了\(j\)个......
  • 时光邮局|来写一封未来的信试试吧!一个我的新项目
    什么是时光邮局?漫漫星河璀璨,漫漫古道长河。官网:云寄-时光邮局寻找一份特殊的意义,学会热爱生活,学会面朝大海。有一天我收到了两年前的自己来信。如果可以给末来寄信......
  • 邮局寄包裹
    【题目描述】小明去邮局给朋友寄礼物。发现邮局对邮寄包裹的费用是这样规定的:如果包裹长宽高任意一个尺寸超过1米,或重量超过30千克,不予邮寄;对可以邮寄的包裹每件收手续费0.2......