山区建小学(递推,区间dp)

【题目描述】
政府在某山区修建了一条道路,恰好穿越总共m个村庄的每个村庄一次,没有回路或交叉,任意两个村庄只能通过这条路来往。已知任意两个相邻的村庄之间的距离为di(为正整数),其中,0

【输入】
第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
题目分析:
这个题需要学习一个区间dp,不懂的话就去搜一下把,懂了的话这个题大部分都懂了。
既然这个题处在了递推当中就要把这个问题往规模小的方向去想。这时候不难想到在每个村庄都建一个小学的时候最短距离为零。除了这个就很难想到别的了,所以就要对问题进行分解。
一个学校管辖一个范围内的村庄。那么我们设s[i][j]表示,从编号为i的村庄到编号为j的村庄之间只建一个小学的最短距离。
这里普及一个知识:一段区间内所有点到某个地方距离最短的话,那么那个点一定在这个区间的中点。
设f[i][j]表示前i个村庄建j个小学使得每个村庄到小学距离最小距离的和。
我们可以这么想,假设从编号k开始到编号m结束的这么一个区间里只建一所小学(最短距离为s[k][j]),那么在编号1开始k-1结束的这个区间里就要建j-1个小学(f[k-1][j-1])。那么状态转移方程就推出来了:f[i][j]=f[k-1][j-1]+s[k][j]。
代码:

#include
#include
#include//其中包含常量INT_MAX
using namespace std;
int a[1001],f[1001][1001],s[1001][1001];
int lu(int,int);//计算编号从left到right的村庄的中间学校1的距离。
int main()
{
    int m,n,dis;
    cin>>m>>n;
    for(int i=2;i<=m;i++)
    {
        cin>>dis;
        a[i]=dis+a[i-1];//第i个村庄在数轴上的坐标。
    }
    for(int i=0;i<m;i++)
        for(int j=i;j<=m;j++)//这里j从i开始的原因时j必须要大于等于i,因为s是从i到j村庄距离和。
    {
        s[i][j]=lu(i,j);
        s[j][i]=s[i][j];
    }
    for(int i=1;i<=m;i++)
      for(int j=1;j<=i&&j<=n;j++)
        f[i][j]=INT_MAX;
    for(int i=1;i<=m;i++) f[i][i]=0;//当学校数目和村庄数目相等时。
    for(int i=1;i<=m;i++)//只建一个学校时
        f[i][1]=s[1][i];
    for(int i=2;i<=m;i++)//有几个村庄,这里懒得思考了就把所有可能出现的值全部赋值了。
        for(int j=2;j<=n&&j<=i;j++)//有几个学校,注意j<=i,不可能学校比村庄还多把!!
    {
        for(int k=j;k<=i;k++)//注意这里!k一定要从j开始算起!
       if(i!=j) f[i][j]=min(f[i][j],f[k-1][j-1]+s[k][i]);
    }
    cout<<f[m][n]<<endl;//输出m个村庄建n所小学的最小的距离。
    return 0;
}
int lu(int left,int right)
{
    int mid=(left+right)/2,l=0;
    for(int i=left;i<=right;i++)
    {
        l+=abs(a[mid]-a[i]);
    }
    return l;
}

如果看了一遍还是不会的话就多想一会儿,我写的时候还感觉有点力不从心,没有完全理解透。

输入:
10 2
输出:
3 1 3 1 1 1 1 1 3
18

Process returned 0 (0x0)   execution time : 3.384 s
Press any key to continue.

你可能感兴趣的:(算法学习)