【题目链接】
ybt 1197:山区建小学
OpenJudge 2.6 7624:山区建小学
洛谷 P4677 山区建小学
【题目考点】
1. 动态规划:区间动规
2. 前缀和
【解题思路】
1. 求相邻多村中建一所小学,各村上学的最短距离
现在准备在第i村到第j村中建立一所小学,从第i村到第j村的学生都只能上这一所小学。考虑将小学建在哪个村里,可以使得第i到第j各村的学生上学距离加和最短?
上图用一条线段上的点表示各个村,包括第i,i+1,i+2,…,j-1,j村。
相邻两村之间的距离是已知的,已知第
x
x
x到第
x
+
1
x+1
x+1村的距离为
d
x
d_x
dx,设在第p村建学校。
证明:
p
=
i
+
j
2
p=\frac{i+j}{2}
p=2i+j时,在第
p
p
p村建学校,各村上学的距离总和最小。
c
i
,
j
c_{i,j}
ci,j表示从第i村到第j村建只一所小学,且从第i村到第j村都上这一所小学,上学的距离加和的最小值。根据上述结论,应该在第
i
+
1
2
\frac{i+1}{2}
2i+1村建小学。
该问题可以作为一个小的动态规划问题来解,
c
i
,
j
c_{i,j}
ci,j为状态
- 初始状态: c i , i c_{i,i} ci,i,从第i村到第i村建1所小学,那么就在第i村建,第i村的学生上学走路距离为0,所以对于任意i,有 c i , i = 0 c_{i,i}=0 ci,i=0
- 状态转移:
- 已知从第i村到第j-1村,在第 i + j − 1 2 \frac{i+j-1}{2} 2i+j−1村建小学,距离总和为 c i , j − 1 c_{i,j-1} ci,j−1。
- 现在要考虑第i村到第j村,到第 i + j − 1 2 \frac{i+j-1}{2} 2i+j−1村的小学上学,距离总和为 c i , j − 1 + ∑ x = ( i + j − 1 ) / 2 j − 1 d x c_{i,j-1}+\sum_{x=(i+j-1)/2}^{j-1}d_x ci,j−1+∑x=(i+j−1)/2j−1dx。
- 现在要将在第
i
+
j
−
1
2
\frac{i+j-1}{2}
2i+j−1村的小学移到第
i
+
j
2
\frac{i+j}{2}
2i+j村。
- 如果i+j是奇数,那么 i + j − 1 2 = i + j 2 \frac{i+j-1}{2} = \frac{i+j}{2} 2i+j−1=2i+j,小学没有移动。距离总和可以写为: c i , j − 1 + ∑ x = ( i + j ) / 2 j − 1 d x c_{i,j-1}+\sum_{x=(i+j)/2}^{j-1}d_x ci,j−1+∑x=(i+j)/2j−1dx
- 如果i+j是偶数,那么
i
+
j
−
1
2
=
i
+
j
2
−
1
\frac{i+j-1}{2} = \frac{i+j}{2}-1
2i+j−1=2i+j−1,
根据引理1:将学校从第 i + j 2 − 1 \frac{i+j}{2}-1 2i+j−1村移到第 i + j 2 \frac{i+j}{2} 2i+j村,上学总距离减少: s d = ( i + j + 1 − 2 ∗ i + j 2 ) d ( i + j ) / 2 − 1 = d ( i + j ) / 2 − 1 sd=(i+j+1-2*\frac{i+j}{2})d_{(i+j)/2-1}=d_{(i+j)/2-1} sd=(i+j+1−2∗2i+j)d(i+j)/2−1=d(i+j)/2−1,距离总和为: c i , j − 1 + ∑ x = ( i + j − 1 ) / 2 j − 1 d x − d ( i + j ) / 2 − 1 = c i , j − 1 + ∑ x = ( i + j ) / 2 − 1 j − 1 d x − d ( i + j ) / 2 − 1 = c i , j − 1 + ∑ x = ( i + j ) / 2 j − 1 d x c_{i,j-1}+\sum_{x=(i+j-1)/2}^{j-1}d_x-d_{(i+j)/2-1}=c_{i,j-1}+\sum_{x=(i+j)/2-1}^{j-1}d_x-d_{(i+j)/2-1}=c_{i,j-1}+\sum_{x=(i+j)/2}^{j-1}d_x ci,j−1+∑x=(i+j−1)/2j−1dx−d(i+j)/2−1=ci,j−1+∑x=(i+j)/2−1j−1dx−d(i+j)/2−1=ci,j−1+∑x=(i+j)/2j−1dx
因此,得到状态转移方程为
c
i
,
j
=
c
i
,
j
−
1
+
∑
x
=
(
i
+
j
)
/
2
j
−
1
d
x
c_{i,j}=c_{i,j-1}+\sum_{x=(i+j)/2}^{j-1}d_x
ci,j=ci,j−1+∑x=(i+j)/2j−1dx
距离加和可以由前缀和方便地求出。
记
s
i
s_i
si表示第i村到第1村的距离,那么
s
i
=
∑
x
=
1
i
−
1
d
x
s_i=\sum_{x=1}^{i-1}d_x
si=∑x=1i−1dx
那么第i村到第j村的距离为
s
j
−
s
i
s_j-s_i
sj−si
所以状态转移方程可以写为:
c
i
,
j
=
c
i
,
j
−
1
+
s
j
−
s
(
i
+
j
)
/
2
c_{i,j}=c_{i,j-1}+s_j-s_{(i+j)/2}
ci,j=ci,j−1+sj−s(i+j)/2
即c[i][j] = c[i][j-1] + s[j] - s[(i+j)/2]
另一种写法,也可以直接通过前缀和求c数组。
求从第i村到第j村每个村到第
i
+
1
2
\frac{i+1}{2}
2i+1村的距离加和
c
i
,
j
=
∑
x
=
i
(
i
+
j
)
/
2
−
1
(
s
(
i
+
j
)
/
2
−
s
x
)
+
∑
x
=
(
i
+
j
)
/
2
+
1
j
(
s
x
−
s
(
i
+
j
)
/
2
)
c_{i,j} = \sum_{x=i}^{(i+j)/2-1}(s_{(i+j)/2}-s_x)+\sum_{x=(i+j)/2+1}^{j}(s_x-s_{(i+j)/2})
ci,j=∑x=i(i+j)/2−1(s(i+j)/2−sx)+∑x=(i+j)/2+1j(sx−s(i+j)/2)
本质上和第一种写法是一样的。
2. 求前i个村建j个学校的最小距离和
- 确定状态定义:
集合:建学校的方案
限制:哪些村,建学校个数
属性:各村到最近学校距离和
条件:最小
统计量:各村到最近学校距离和
状态定义:dp[i][j]
: 前i个村建j所小学,且前i个村就只去这j所小学的情况下,各村到最近学校的距离和。
初始状态:前i个村建1所小学,距离和为 c 1 , i c_{1,i} c1,i,所以dp[i][1] = c[1][i]
- 确定状态转移方程:
前i个村要建j所小学,考虑最后建的一所小学要让哪些使用村。
前i-1个村建j-1所小学,第j所小学给第i村使用。dp[i][j] = dp[i-1][j-1]+c[i][i]
前i-2个村建j-1所小学,第j所小学给第i-1到第i村使用。dp[i][j] = dp[i-2][j-1]+c[i-1][i]
前i-3个村建j-1所小学,第j所小学给第i-2到第i村使用。dp[i][j] = dp[i-3][j-1]+c[i-2][i]
…
前k个村建j-1所小学,第j所小学给第k+1到第i村使用,在第k+1到第i村建一所使得这些村的学生上学距离加和最短的小学,需要在第 k + 1 + i 2 \frac{k+1+i}{2} 2k+1+i村建校,距离加和为c[k+1][i]
。所以总加和为:dp[i][j] = dp[k][j-1]+c[k+1][i]
k最小为j-1,即前j-1个村建j-1所小学,每个村一所,k最大为i-1,这样可以在第i村建第j所学校。
状态转移方程为:
k从j-1循环到i-1,求dp[i][j] = dp[k][j-1]+c[k+1][i]
的最小值。
前i个村建j所小学,有m个村,最多n个小学。
i从1循环到m,
j从1循环到n。如果
j
>
=
i
j>=i
j>=i,可以保证每个村一所学校,总距离为0。所以j只需要循环到
i
−
1
i-1
i−1即可。所以j的循环条件为
j
<
=
n
j<=n
j<=n且
j
<
i
j<i
j<i。
【题解代码】
解法1:区间动规
- 通过递推求c数组
#include<bits/stdc++.h>
using namespace std;
#define N 505
#define INF 0x3f3f3f3f
int d[N], s[N];//d[i]:第i村到第i+1村的距离 s[i]:第i村到第1村的距离
int c[N][N];//c[i][j]:第i村到第j村建1所小学,且都上这所小学的最小距离
int dp[N][N];//dp[i][j]:前i个村建j所小学,且都上这些小学的最小距离
int main()
{
int n, m;
cin >> m >> n;
for(int i = 1; i <= m - 1; ++i)
{
cin >> d[i];
s[i+1] = s[i] + d[i];
}
for(int i = 1; i <= m; ++i)
for(int j = i; j <= m; ++j)
c[i][j] = c[i][j-1] + s[j] - s[(i+j)/2];
for(int i = 1; i <= m; ++i)
dp[i][1] = c[1][i];
for(int i = 1; i <= m; ++i)
for(int j = 2; j <= n && j < i; ++j)//j==i时,每个村一所学校,不用
{
dp[i][j] = INF;
for(int k = j - 1; k <= i - 1; ++k)
dp[i][j] = min(dp[i][j], dp[k][j-1] + c[k+1][i]);
}
cout << dp[m][n];
return 0;
}
- 通过前缀和直接求c数组
#include<bits/stdc++.h>
using namespace std;
#define N 505
#define INF 0x3f3f3f3f
int d[N], s[N];//d[i]:第i村到第i+1村的距离 s[i]:第i村到第1村的距离
int c[N][N];//c[i][j]:第i村到第j村建1所小学,且都上这所小学的最小距离
int dp[N][N];//dp[i][j]:前i个村建j所小学,且都上这些小学的最小距离
int main()
{
int n, m;
cin >> m >> n;
for(int i = 1; i <= m - 1; ++i)
{
cin >> d[i];
s[i+1] = s[i] + d[i];
}
for(int i = 1; i <= m; ++i)
for(int j = i; j <= m; ++j)
{
int mid = (i+j)/2;
for(int k = i; k <= mid-1; ++k)
c[i][j] += s[mid] - s[k];
for(int k = mid+1; k <= j; ++k)
c[i][j] += s[k] - s[mid];
}
for(int i = 1; i <= m; ++i)
dp[i][1] = c[1][i];
for(int i = 1; i <= m; ++i)
for(int j = 2; j <= n && j < i; ++j)//j==i时,每个村一所学校,不用
{
dp[i][j] = INF;
for(int k = j - 1; k <= i - 1; ++k)
dp[i][j] = min(dp[i][j], dp[k][j-1] + c[k+1][i]);
}
cout << dp[m][n];
return 0;
}