信息学奥赛一本通1197:山区建小学

【题目描述】

政府在某山区修建了一条道路,恰好穿越总共m𝑚个村庄的每个村庄一次,没有回路或交叉,任意两个村庄只能通过这条路来往。已知任意两个相邻的村庄之间的距离为di𝑑𝑖(为正整数),其中,0<i<m0<𝑖<𝑚。为了提高山区的文化素质,政府又决定从m𝑚个村中选择n𝑛个村建小学(设0<n≤m<5000<𝑛≤𝑚<500)。请根据给定的m𝑚、n𝑛以及所有相邻村庄的距离,选择在哪些村庄建小学,才使得所有村到最近小学的距离总和最小,计算最小值。

【输入】

第1行为m𝑚和n𝑛,其间用空格间隔

第2行为m−1𝑚−1 个整数,依次表示从一端到另一端的相邻村庄的距离,整数之间以空格间隔。

例如:

10 3
2 4 6 5 2 4 3 1 3

表示在1010个村庄建33所学校。第11个村庄与第22个村庄距离为22,第22个村庄与第33个村庄距离为44,第33个村庄与第44个村庄距离为66,...,第99个村庄到第1010个村庄的距离为33。

【输出】

各村庄到最近学校的距离之和的最小值。

【输入样例】

10 2
3 1 3 1 1 1 1 1 3

【输出样例】

18

【解题思路】

如图,以k为界,将i个村庄分成两部分,我们只需找到一个数k,使在第一部分各个村庄去最近小学的距离加上在第二部分各个村庄去最近小学的距离最小,那么便能得出此题的解。

因此。解决此题的思路就是算出任意两个村庄(如第一个村庄去第五个村庄)的距离以及在i个村庄里建一所小学,这些村庄去这个小学的最短距离是多少。

定义三个数组:dis[i][j]表示第i个村庄去第j个村庄的距离;c[i][j]表示在第i个村庄和第j个村庄之间建一所小学(例如在第3个村庄和第7个村庄之间建一所小学),第i个村庄和第j个村庄之间的所有村庄到这所小学的最短距离;f[i][j]表示在i个村庄中建j所小学(例如在7个村庄中建3所小学),这i个村庄去最近小学的最短距离。另外,f[i][1]表示在i个村庄中建1所小学,那么f[i][1] = c[1][i]

首先解决dis数组如何存储任意两个村庄之间的距离,首先for循环控制输入,输入的第一个数即为第一个村庄到第二个村庄之间的距离,输入的第二个数即为第二个村庄到第三个村庄之间的距离,以此类推,故cin >> dis[i][i+1]。从第i个村庄到第j个村庄的距离就等于从第i个村庄去第j-1个村庄,再从第j-1个村庄去第j个村庄之间的距离,递推式为dis[i][j] = dis[i][j-1]+dis[j-1][j]

然后亟待我们解决的是第i个村庄到第j个村庄之间建一所小学,从第i到第j这个区间的所有村庄去小学的最近距离。首先要明确一点,将小学在在中间的村庄,这样总距离最短,即建在第(i+j)/2个村庄。

证明:①当村庄数为奇数个时:

②当村庄数为偶数个时:

经过证明,我们可以得知当在第i个村庄和第j个村庄之间建小学时,建在第(i+j)/2个村庄可以保证最优解。由此可知c[i][j] += dis[k][mid](k遍历i到j区间的所有村庄,mid = (i+j)/2)

最后,我们要解决当要在i个村庄里建j所小学,这些村庄到最近小学的最短距离。双层for循环遍历  i和j必不可少,此外,就像开头说的“我们只需找到一个数k,使在第一部分各个村庄去最近小学的距离加上在第二部分各个村庄去最近小学的距离最小”,还应有一层循环用于遍历将村庄分成两部分的k,判断以哪个村庄为界,将这i个村庄分成两部分所得总距离最小,最小值保存在f[i][j]中。由于这i个村庄被分成了两部分,一部分是在k个村庄中建j-1所小学,另一部分是在第k+1到第i个村庄中建1所小学,因此递推式为f[i][j] = min(f[i][j],f[k][j-1]+c[k+1][i])。注意:k需遍历从j-1到i,这期间最小的解将保留,因此用min函数来保存距离最小的解。

【题解代码】

#include <bits/stdc++.h>
using namespace std;
int dis[501][501],c[501][501],f[501][501];
int main(){
	int m,n;	//m个村庄 ,n个小学 
	cin >> m >> n;
	for (int i=1;i<m;i++) cin >> dis[i][i+1]; 
	for (int i=1;i<m;i++){
		for (int j=i+1;j<=m;j++){
			dis[i][j] = dis[i][j-1]+dis[j-1][j];
			dis[j][i] = dis[i][j];
		}
	}
	for (int i=1;i<m;i++){    //计算在第i和第j个村庄之间建一所小学的最短距离 
		for (int j=i+1;j<=m;j++){
			int mid=(i+j)/2;    //在两个村庄之间建小学距离最短 
			for (int k=i;k<=j;k++) c[i][j] += dis[k][mid];	
		}
	}
	memset(f,0x7f,sizeof(f));    //将f数组各个元素初始化为int型所储存的最大数,方便后续的比较
	for (int i=1;i<=m;i++) f[i][1] = c[1][i];    //f[i][j]表示在i个村庄里建j所小学的最短距离  
	for (int i=1;i<=m;i++){
		for (int j=2;j<=n;j++)
			for (int k=j-1;k<=i;k++) f[i][j] = min(f[i][j],f[k][j-1]+c[k+1][i]);	    
	}
	cout << f[m][n];
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值