选取数对

选取数对

给定一个长度为 n 的整数数列 a1,a2,,an

请你选择 k 个数对 [l1,r1],[l2,r2],,[lk,rk],要求所选数对满足:

  1. 1l1r1<l2r2<<lkrkn
  2. 对于 1ikrili+1=m 均成立。
  3. sum=i=1kj=liriajsum 的值应尽可能大。

请你输出 sum 的最大可能值。

输入格式

第一行包含三个整数 n,m,k

第二行包含 n 个整数 a1,a2,,an

输出格式

一个整数,表示 sum 的最大可能值。

数据范围

6 个测试点满足 1m×k×n20
所有测试点满足 1m×kn50000ai109

输入样例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(n3),会超时,因此需要优化。

  我们可以发现,当j固定的时候,那么k的起点也就是最小值就是(j1)m固定不变。枚举i,每当i1,那么k的最大值就增加1,也就是只会增加多一个f(k,j1)。又因为我们是找k这个区间内的f(k,j1)的最大值,因此我们可以用一个变量来维护这个动态区间的最大值。

  比如说,如果要知道f(i,j)的最大值,那么我们需要划分集合,k的取值范围为(j1)mkim,我们要求的是所有的f(k,j1)中的最大值,然后加上sisim。对于f(i+1,j),这时k的取值范围为(j1)mkim+1,一样要求所有的f(k,j1)中的最大值,我们可以发现与f(i,j)相比,就多求了一个f(im+1,j1),这意味着前面的枚举是重复的。因此我们可以开一个变量来动态维护之前的数的最大值,这样就不用每次都从头枚举。

  或者可以这样想,因为每次枚举都是从f(im,j1)开始往前枚举,因此我们可以把f(im,j1)定义为右端点不超过im,且选择了j个区间的所有合法方案的集合,f(im,j1)存的就是最小值,这就变成上面第一种的划分方式了(不合法方案被赋值为负无穷,不会影响取最大值)。

  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 @   onlyblues  阅读(61)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示