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个区间的问题。

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

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

这个算法,i~2~就是i,新学校就是第j所,我们只要枚举i~1~,从所有可行的i~1~中选出最优的给f~i,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数组,就可以进行转移了,根据上文的思路,写出方程:

f~i,j~=min(f~i,j~,f~k,j-1~+s~k,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;
}

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

猜你喜欢

转载自www.cnblogs.com/Wild-Donkey/p/12221297.html