山区建小学(区间型动态规划,递推)

描述
政府在某山区修建了一条道路,恰好穿越总共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;
}
原文地址:https://www.cnblogs.com/zyacmer/p/9903117.html