选取数对

选取数对

给定一个长度为 $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. $1 \leq l_{1} \leq r_{1} < l_{2} \leq r_{2} < \dots < l_{k} \leq r_{k} \leq n$ 。
  2. 对于 $1 \leq i \leq k$,$r_{i}−l_{i}+1=m$ 均成立。
  3. 设 $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/

posted @ 2022-03-21 16:25  onlyblues  阅读(53)  评论(0编辑  收藏  举报
Web Analytics