描述
政府在某山区修建了一条道路,恰好穿越总共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
/* //感谢SilverNebula的https://blog.csdn.net/silvernebula/article/details/51379005 //感谢Always_ease的https://blog.csdn.net/Always_ease/article/details/80527234 思路汇总: 结论1: 不管每个山村之间隔多远 在第i山村到第j个山村中所有点,如果只建一个学校,取(i+j)/2这个山村建学校能使 总距离最小,无论i+j是奇数还是偶数 我的不严谨推理:如果把学校建两边是最差情况,所以尽量往中间建咯 如果是1,2,3,4这种情况建在2和3其实总距离一样,可以自己证一下 结论2: 已经有i个山村j-1个学校,现在建第j个学校,前j-1个山村是绝对不会被挖走而改变路线的 让k=j-1,k表示前k个村庄不改变路线,现在让k一直自增来枚举各种情况(第j个学校到底改变了几个村庄) 再找出各种k中最好的情况就好了 也就是第j个学校的出现把局面分成了 第1到k各山村和第k到i个山村,第i个山村只有一个学校,也就是那个新学校,所以很好求 前1到k个山村,就是考虑k个村庄,j-1个学校,这些都是前面递推出来的,直接套用前面的 步骤: 1.计算任意两地之间距离,存入数组a 2.计算任意两地之间如果只有只有一个学校,那么两地之间所有村庄到这个学校 的距离和的最小值(两地中间建学校,距离总和最小) 3.为f数组赋上初值,即当1个学校匹配任意村庄的距离和 4.依次考虑加入新学校后有多少村庄为此改变路线,找出最好情况 */ //我好弱啊,每题都要看答案才能学会做出来 #include<bits/stdc++.h> using namespace std; #define ll long long const ll maxn=520; ll a[maxn][maxn]={0},c[maxn][maxn],f[maxn][maxn]; //数组a:两个点之间距离;;;数组c:闭区间[i,j]内所有点到最近学校的距离和 //数组f:f[i][j]表示i个村庄j个学校所有村庄到最近学校的距离总和 int main() { ll i,n,t,j,k,l,m,mid,s=INT_MAX;//s是一个很大非常大的值 cin>>m>>n;//m为村庄,n为小学 for(i=1;i<m;i++) scanf("%lld",&a[i][i+1]);//依次输入第i个村庄到下一个村庄的距离 for(i=1;i<=m;i++)//遍历每一种i,j配对 for(j=i+1;j<=m;j++) {//数组a:两个点之间距离; a[i][j]=a[i][j-1]+a[j-1][j];//比如说a[2][5]=a[2][4]+a[4][5], //先考虑a[j-1][j]前面已经输入过,再考虑a[i][j]-a[j-1][j]=a[i][j-1], //而a[i][j-1]这个数据在a[i][j],循环到a[i][j]时a[i][j-1]已经计算完了, //所以等式成立; //其中a[2][3]和a[3][4]在前面输入时就已经给出了 a[j][i]=a[i][j];//镜像复制刚刚只考虑i<j情况,现在给出另一半 //比如a[2][4]=a[4][2] } for(i=1;i<=m;i++)//遍历每一种i,j配对 for(j=i+1;j<=m;j++) { mid=(i+j)/2;//让mid锁定在序号为中间的村庄 //@1,@2,@3,@4=>>>>@2 (1+4)/2=2 //@1,@2,@3=>>>>@2 (1+3)/2=2 c[i][j]=0;//先让闭区间[i,j]内所有点到最近学校的距离和为0 //数组c:闭区间[i,j]内所有点到最近学校的距离和 for(k=i;k<=j;k++)//枚举闭区间[i,j]的各个点为k c[i][j]+=a[k][mid];//c[i][j]+=[i,j]内各个点到mid点的距离和 } for(i=1;i<=m;i++) f[i][1]=c[1][i]; //赋初值,i个村庄只有一个学校时答案是确定的,就是这区间内所有点到中间点的距离和 for(i=1;i<=m;i++) for(j=2;j<=n;j++)//一个学校的情况考虑过了,现在j可以从2开始算 { f[i][j]=s;//先把每种情况的答案初始化为很大的值 for(k=j-1;k<=i;k++)//枚举已有的学校管辖的范围//k确实最小值为j-1 f[i][j]=min(f[i][j],f[k][j-1]+c[k+1][i]);//找出最小值 //在for循环中,f[i][j]经常改变,所以要让f[i][j]和k对应情况比较多次 }//拿j和j-1时比较 /* 把j看作新学校,本来已经有i个村庄和j-1个学校,现在考虑有多少个村庄要投靠新学校i 毫无疑问,如果原来j-1个旧学校,那么能保证j-1位及之前村庄不会改变 */ cout<<f[m][n]<<endl; return 0; }