luogu P4677 山区建小学

山区建小学

题目描述

政府在某山区修建了一条道路,恰好穿越总共\(n\)个村庄的每个村庄一次,没有回路或交叉,任意两个村庄只能通过这条路来往。已知任意两个相邻的村庄之间的距离为\(d_i\)(为正整数),其中,\(0<i<n\)。为了提高山区的文化素质,政府又决定从\(n\)个村中选择\(m\)个村建小学。请根据给定的\(n\)\(m\)以及所有相邻村庄的距离,选择在哪些村庄建小学,才使得所有村到最近小学的距离总和最小,计算最小值。

输入格式

第1行为n和m,其间用空格间隔。

第2行为n-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。

输出格式

各村庄到最近学校的距离之和的最小值。

样例 #1

样例输入 #1

10 2
3 1 3 1 1 1 1 1 3

样例输出 #1

18

提示

0 < \(m\) <= \(n\) < 500

0 < \(d_i\) <=100

思路

我们首先考虑一个性质
就是一段区间我们肯定是在中间插点才是最小的 (只插一个)
证明:
首先我们设左边有l个点 右边r个点
假设我们现在就在中间点 我们往左移一单位就是总距离之和+r-l 而一直往左只会让r更多
所以这个是个单调减的 左边也是如此
所以我们知道一段区间中间才是最好的
我们预处理出每个区间的f[l][r]表示区间内的距离之和的min
问题就转化为了我们一个大区间怎么选m个小区间正好全部放满的同时还要最小
我们直接设dp[i][j]表示前[1,ai]区间选了j个区间
而我们状态转移只要枚举右端点在ai的区间即可
dp[i][j]=min{dp[k][j-1]+f[k+1][i]}

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6+10;
const int M = 998244353;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define all(x) (x).begin(),(x).end()
#define YES cout<<"YES"<<endl;
#define NO cout<<"NO"<<endl;
#define _ 0
#define pi acos(-1)
#define INF 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int a[510],dp[510][510],f[510][510];
void solve() {
    int n,m;cin>>n>>m;
    for(int i=2;i<=n;i++){
        cin>>a[i];
        a[i]+=a[i-1];
    }
    for(int i=1;i<=n;i++)
        for(int j=i;j<=n;j++){
            int mid=i+j>>1;
            for(int k=i;k<=j;k++)f[i][j]+=abs(a[mid]-a[k]);
        }
    memset(dp,0x3f3f,sizeof dp);
    dp[0][0]=0;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=min(i,m);j++)
            for (int k = j - 1; k <= i - 1; k++)
                dp[i][j] = min(dp[i][j], dp[k][j - 1] + f[k + 1][i]);
    cout<<dp[n][m]<<endl;
}
signed main(){
    fast
    int T;T=1;
    while(T--) {
        solve();
    }
    return ~~(0^_^0);
}
posted @ 2022-09-28 00:27  ycllz  阅读(41)  评论(0编辑  收藏  举报