选取数对
选取数对
给定一个长度为 $n$ 的整数数列 $a_{1},a_{2}, \dots ,a_{n}$。
请你选择 $k$ 个数对 $\left[ {l_{1},r_{1}} \right],\left[ {l_{2},r_{2}} \right], \dots ,\left[ {l_{k},r_{k}} \right]$,要求所选数对满足:
- $1 \leq l_{1} \leq r_{1} < l_{2} \leq r_{2} < \dots < l_{k} \leq r_{k} \leq n$ 。
- 对于 $1 \leq i \leq k$,$r_{i}−l_{i}+1=m$ 均成立。
- 设 $sum=\sum\limits_{i = 1}^{k}{\sum\limits_{j = l_{i}}^{r_{i}}a_{j}}$,$sum$ 的值应尽可能大。
请你输出 $sum$ 的最大可能值。
输入格式
第一行包含三个整数 $n,m,k$。
第二行包含 $n$ 个整数 $a_{1},a_{2}, \dots ,a_{n}$。
输出格式
一个整数,表示 $sum$ 的最大可能值。
数据范围
前 $6$ 个测试点满足 $1 \leq m \times k \times n \leq 20$。
所有测试点满足 $1 \leq m \times k \leq n \leq 5000$,$0 \leq a_{i} \leq {10}^{9}$。
输入样例1:
5 2 1 1 2 3 4 5
输出样例1:
9
输入样例2:
7 1 3 2 10 7 18 5 33 0
输出样例2:
61
解题思路
这题是从若干个长度为$m$的区间里面选择$k$个,求选择的这$k$个区间和的最大值,并且这$k$个区间两两不能有交集。
这题是用动态规划做的。
AC代码如下:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 6 typedef long long LL; 7 8 const int N = 5010; 9 10 LL s[N], f[N][N]; 11 12 int main() { 13 int n, m, k; 14 scanf("%d %d %d", &n, &m, &k); 15 for (int i = 1; i <= n; i++) { 16 int val; 17 scanf("%d", &val); 18 s[i] = s[i - 1] + val; 19 } 20 21 for (int i = 1; i <= n; i++) { 22 for (int j = 1; j <= k; j++) { 23 f[i][j] = f[i - 1][j]; 24 if (i - m >= 0) f[i][j] = max(f[i][j], f[i - m][j - 1] + s[i] - s[i - m]); 25 } 26 } 27 28 printf("%lld", f[n][k]); 29 30 return 0; 31 }
这种划分方式的时间复杂度为$O \left( n^{3} \right)$,会超时,因此需要优化。
我们可以发现,当$j$固定的时候,那么$k$的起点也就是最小值就是$\left( {j-1} \right) \cdot m$固定不变。枚举$i$,每当$i$加$1$,那么$k$的最大值就增加$1$,也就是只会增加多一个$f \left( {k, j-1} \right)$。又因为我们是找$k$这个区间内的$f \left( {k, j-1} \right)$的最大值,因此我们可以用一个变量来维护这个动态区间的最大值。
比如说,如果要知道$f \left( {i, j} \right)$的最大值,那么我们需要划分集合,$k$的取值范围为$\left( {j-1} \right) \cdot m \leq k \leq i-m$,我们要求的是所有的$f \left( {k, j-1} \right)$中的最大值,然后加上$s_{i} - s_{i-m}$。对于$f \left( {i+1, j} \right)$,这时$k$的取值范围为$\left( {j-1} \right) \cdot m \leq k \leq i-m+1$,一样要求所有的$f \left( {k, j-1} \right)$中的最大值,我们可以发现与$f \left( {i, j} \right)$相比,就多求了一个$f \left( {i-m+1, j-1} \right)$,这意味着前面的枚举是重复的。因此我们可以开一个变量来动态维护之前的数的最大值,这样就不用每次都从头枚举。
或者可以这样想,因为每次枚举都是从$f \left( {i - m, j - 1} \right)$开始往前枚举,因此我们可以把$f \left( {i - m, j - 1} \right)$定义为右端点不超过$i-m$,且选择了$j$个区间的所有合法方案的集合,$f \left( {i - m, j - 1} \right)$存的就是最小值,这就变成上面第一种的划分方式了(不合法方案被赋值为负无穷,不会影响取最大值)。
AC代码如下:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 6 typedef long long LL; 7 8 const int N = 5010; 9 10 LL s[N], f[N][N]; 11 12 int main() { 13 int n, m, k; 14 scanf("%d %d %d", &n, &m, &k); 15 for (int i = 1; i <= n; i++) { 16 int val; 17 scanf("%d", &val); 18 s[i] = s[i - 1] + val; 19 } 20 21 for (int j = 1; j <= k; j++) { // 先枚举选择的区间个数 22 LL maxf = 0; 23 for (int i = j * m; i <= n; i++) { // 再枚举区间右端点 24 maxf = max(maxf, f[i - m][j - 1] + s[i] - s[i - m]); // 每枚举一个新的右端点,加入并动态维护所求区间的最大值 25 f[i][j] = maxf; 26 } 27 } 28 29 LL ret = 0; 30 for (int i = m; i <= n; i++) { 31 ret = max(ret, f[i][k]); 32 } 33 printf("%lld", ret); 34 35 return 0; 36 }
另外一种集合的定义如下:所有选择了$i$个区间且最后一个区间的右端点为$j$的合法方案的集合。划分方式和上面的一样。
AC代码如下:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 6 typedef long long LL; 7 8 const int N = 5010; 9 10 LL s[N], f[N][N]; 11 12 int main() { 13 int n, m, k; 14 scanf("%d %d %d", &n, &m, &k); 15 for (int i = 1; i <= n; i++) { 16 int val; 17 scanf("%d", &val); 18 s[i] = s[i - 1] + val; 19 } 20 21 for (int i = 1; i <= k; i++) { 22 LL maxf = 0; 23 for (int j = i * m; j <= n; j++) { 24 maxf = max(maxf, f[i - 1][j - m] + s[j] - s[j - m]); 25 f[i][j] = maxf; 26 } 27 } 28 29 LL ret = 0; 30 for (int i = m; i <= n; i++) { 31 ret = max(ret, f[k][i]); 32 } 33 printf("%lld", ret); 34 35 return 0; 36 }
参考资料
AcWing 4378. 选取数对(AcWing杯 - 全国联赛):https://www.acwing.com/video/3743/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/16035240.html