山区建小学
题目描述
政府在某山区修建了一条道路,恰好穿越总共\(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);
}
标签:10,int,luogu,距离,村庄,小学,区间,P4677,define
From: https://www.cnblogs.com/ycllz/p/16736559.html