ybt1197;p4677 山区建小学(博客里第一道蓝题)

ybt1197;p4677 山区建小学

【题目描述】

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

【输入】

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

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

例如:

10 3
2 4 6 5 2 4 3 1 3

表示在10个村庄建3所学校。第1个村庄与第2个村庄距离为2,第2个村庄与第3个村庄距离为4,第3个村庄与第4个村庄距离为6,...,第9个村庄到第10个村庄的距离为3。

【输出】

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

【输入样例】

10 2
3 1 3 1 1 1 1 1 3

【输出样例】

18

ybt递推的最后一道题,这是一道蓝题,像我这种弱菜不看题解肯定是做不出来滴!

我们在小学的时候就学过,在数轴上有若干个点,取一点使原来的点到该点距离最小的问题

若点的个数为奇数,那么就取最中间的点;

若个数为偶数,那么在中间两个点之间以及两点连成的线段也是最短的策略。

本题要放置多个学校,所以我们要分解问题。

首先,一个基本推论:每个学校服务的村子应该是连续不断的一个区间。

证明

首先,设一个学校A服务的村庄中最靠左的和最靠右的分别为x和y,那么在A的左边,x的右边,所有的村子到A的距离都比x的要小。而且既然x被A服务,说明它到A左边学校的距离比到A的距离远,所以(x,A)区间内所有村子到A的距离都比到A左边的学校距离近。A右边则同理,所以若x,y被A服务,那么[x,y]区间内所有村子都被A服务。而A所在的村子就是第(x+y)/2个村子(上文的小学知识)

这样一来,问题就变成寻找最合适的方案将m个村子分成n个区间的问题。

这样就可以用fi,j来表示将i个村子分成j个区间的最小距离和。

fi,j的转移可以考虑j从小到大推,j每次加一就相当于在之前基础上新建一所小学,但是由于状态只存储了方案的结果,没有存储学校的具体位置,所以无法决定学校建在哪,所以就考虑在原来的边界i1基础上,向右将边界拓展到i2,这样(i1,i2]区间内所有村子都被新建的学校服务。

这个算法,i2就是i,新学校就是第j所,我们只要枚举i1,从所有可行的i1中选出最优的给fi,j赋值即可。

由于每次转移都要用到第二维为j-1时的数组值,所以j在循环外层。

另外,因为转移过程中会频繁用到任意区间内建一所学校的路程和,所以建一个数组s来预处理。

并且为了计算方便,在读入的同时用前缀和的方式存储每个村子的坐标:

a[1]=0;
for(int i=2;i<=m;i++) {
	cin>>a[i];
	a[i]+=a[i-1];
	//cout<<i<<"	"<<a[i]<<endl;
}
for(int i=1;i<=m;i++) {
	for(int l=1;l<=m-i;l++) {
		s[l][l+i]=s[l][l+i-1]+a[l+i]-a[l+(i/2)];//这个递推方程(我也懒得)证明,总之画图找一下规律就出来了
		//cout<<l<<" "<<i<<" "<<s[l][l+i]<<endl;
	}
}

处理好s数组,就可以进行转移了,根据上文的思路,写出方程:

fi,j=min(fi,j,fk,j-1+sk,i)

接下来上AC代码:

#include<iostream>
#include<cstring>
using namespace std;
long long f[505][505],a[505],s[505][505],m,n,Ans=0;//邻村距离,m个村,n个学校 
int main() {
	cin>>m>>n;
	a[1]=0;
	for(int i=2;i<=m;i++) {
		cin>>a[i];
		a[i]+=a[i-1];
		//cout<<i<<"	"<<a[i]<<endl;
	}
	for(int i=1;i<=m;i++) {
		for(int l=1;l<=m-i;l++) {
			s[l][l+i]=s[l][l+i-1]+a[l+i]-a[l+(i/2)];
			//cout<<l<<" "<<i<<" "<<s[l][l+i]<<endl;
		}
	}
	memset(f,0x7f,sizeof(f));//因为后面要取最小值,所以给f数组附上较大的初始值,保证f数组在处理过程中被更新。
	for(int i=1;i<=m;i++){
		f[i][1]=s[1][i];
	}
	for(int j=2;j<=n;j++) {//枚举学校数 
		for(int i=j;i<=m;i++) {//枚举村子数(不得小于学校数) 
			for(int k=1;k<i;k++) {//枚举第j所学校的服务区间(前文提到,左开右闭)左边界(不得大于或等于总村子数i) 
				f[i][j]=min(f[i][j],f[k][j-1]+s[k+1][i]);
				//cout<<i<<" "<<j<<"	"<<f[i][j]<<endl;
			} 
		}
	}
	cout<<f[m][n]<<endl;
	return 0;
}

感谢题解的启发(只是看了思路,其实文章和推理都是自己写的)

posted @ 2020-01-21 12:01  Wild_Donkey  阅读(384)  评论(0编辑  收藏  举报