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;
}
感谢题解的启发(只是看了思路,其实文章和推理都是自己写的)