题目概述:
给定一个长度为 𝑛 的 01 串,定义如下操作为一次 “切割”:
将长度大于 1 的字符串分割为两个非空的连续字串,记分割出来的左侧字串 a 中 0 的出现次数为
C 0 C_0 C0,右侧字串 b 中 1 出现的次数为 C 1 C_1 C1 ,需要满足 L ≤ L \leq L≤ | C 0 − C 1 C_0 - C_1 C0−C1| ≤ R \leq R ≤R
你每次切割完,都会得到两个新 01 串,你可以继续选择这些已经被你切出来的 01 串做切割,只要满足切割条件。
jackle 想问你最多可以切割多少次?
输入描述:
第一行输入3个整数,n( 1 ≤ 1 \leq 1≤ n ≤ 500 \leq 500 ≤500, L , R ( 0 ≤ L,R (0\leq L,R(0≤ L ≤ R L \leq R L≤R ≤ 500 ) \leq 500) ≤500),分别表示字符串长度,和题目中的两个参数。
第二行输入1个长度为n的01串。
输出描述:
输出最多能切割多少次
事例
输入
6 2 3
011011输出
3
说明
其中一种切割次数最多的切法如下:
第一次切割可以切:0 ∣ 11011,然后选择 11011 这个串继续做切割。
第二次切割可以切:1 ∣ 1011,然后选择 1011 这个串继续做切割。
第三次切割可以切:1 ∣ 011。
分析题目: 看到题目中的最多,就应该想到需要用动态规划来解决(当时做的时候懵逼了,没有想出来)。题目就是让我们找有多少个字串满足切割条件,也就是求字串的数量只不过加上了一个限定条件,这样大概框架就有了。
DP
状态表示:dp[i][j],表示左端点为i,右端点为j的所有满足条件的字串的集合
状态计算:dp[i][j]=max(dp[i][j], dp[i][k] + dp[k][j] + 1) (k 为切割点)
代码表示:
#include <iostream>
#include <algorithm>
using namespace std;const int N = 505;
// 分别表示长度为N的字符串中0的数量和1的数量
int sum0[N], sum1[N], dp[N][N];
int n, l, r;
string s;
int main(){cin >> n >> l >> r;cin >> s;// 求前缀和// 这里因为我们的前缀和需要从1开始,但字符串是从0开始所以sum0 sum1是i+1才对for(int i = 0; i < s.length(); i ++){sum0[i + 1] = sum0[i] + (s[i]=='0');sum1[i + 1] = sum1[i] + (s[i]=='1');}// 求字串的模板// 字串长度不能取到n,否则就不是字串了for(int len = 1; len < n; len ++){// i为字串的起始位置for(int i = 1; i + len <= n; i ++){// 切割点的位置不能到字串长度的最后一位,否则右字串将是空串for(int j = i; j < i + len; j ++){int c0 = sum0[j] - sum0[i - 1];int c1 = sum1[i + len] - sum1[j];int sub = abs(c0 - c1);if(sub >= l && sub <= r){dp[i][i + len] = max(dp[i][i + len], dp[i][j] + dp[j + 1][i + len] + 1);}}}}cout << dp[1][n];return 0;
}
此题不算难题,但做的时候卡了挺久,就是因为没看出题的本质。
有不懂的地方可以评论区留言,欢迎点赞关注加收藏!