zoukankan      html  css  js  c++  java
  • 山区建小学(区间型动态规划,递推)

    描述
    政府在某山区修建了一条道路,恰好穿越总共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;
    }
  • 相关阅读:
    Java虚拟机(第二版) 学习笔记之Class类文件的结构
    JVM之深入浅出之垃圾收集算法
    Java虚拟机(第二版) 学习笔记之OutOfMemoryError
    Java虚拟机(第二版) 学习笔记
    平滑加权轮询负载均衡(轮询)算法
    java AQS(AbstractQueuedSynchronizer)同步器详解
    mybatis Interceptor拦截器代码详解
    aspectj编程简介
    Java并发编程阅读笔记-Java监视器模式示例
    我们该怎么结合日志做优化
  • 原文地址:https://www.cnblogs.com/zyacmer/p/9903117.html
Copyright © 2011-2022 走看看