题目描述
jackle 在校赛的时候出过一道 “切割 01 串” 的题目,如今他又出了一道切割 01 串的题目:
给定一个长度为 n 的 01 串,定义如下操作为一次 “切割”:
- 将长度大于 1 的字符串分割为两个非空的连续字串,记分割出来的左侧字串 a 中 0 的出现次数为 C0,右侧字串 b 中 1 出现的次数为 C1,需要满足 L≤∣C0−C1∣≤R。
你每次切割完,都会得到两个新 01 串,你可以继续选择这些已经被你切出来的 01 串做切割,只要满足切割条件。
jackle 想问你最多可以切割多少次?
我的理解
简单来说,也就是给定义我们一个字符串,然后问我们,这个字符串需要每次按照分隔如下规律进行分隔,左侧字串 a a a 中 0 0 0的出现次数为 C 0 C_0 C0 ,右侧字串 b b b 中 1 1 1 出现的次数为 C 1 C_1 C1 ,并且满足 L ≤ ∣ C 0 − C 1 ∣ ≤ R L≤∣ C_0 − C_1∣≤R L≤∣C0−C1∣≤R.之后我将会把以上判断称之为 c h e c k check check函数, 然后问我们最多可以切割该字符串多少次.
思路
check函数
对于check函数,我们需要判断,他是否满足左分隔字符串0的出现次数 C 0 C_0 C0 减去右分隔字符串1的出现次数 C 1 C_1 C1是否在 L L L~ R R R之间,这里最简单的就是直接暴力遍历出左分隔字符串和右分隔字符串,然后分别计算出他们的次数,然后相减,显然,他会多次遍历重复的字符串,那么我们可以使用前缀和优化一下,这里我就不多阐述了,不会前缀和的话可以网上查一下,或者看如下代码:
// left[i]表示1~i中0出现的次数,right[i]表示1~i中1出现的次数
std::vector<int> left(n + 10,0),right(n + 10,0);
for(int i = 0; i < s.size(); i++){
left[i] = left[i - 1] + (s[i] == '0');
right[i] = right[i - 1] + (s[i] == '1');
}
然后我们就可以如下通过判断
// l是左分隔区间的左端点,r是右分割区间的右端点,k是分隔点
// 如果return true也就是可以满足条件啦
int cnt = abs((left[k] - left[l - 1]) - (right[r] - right[k]));
return (cnt >= L && cnt <= R)
现在我们就知道怎么判断是否可以分隔了,接下来,我们就进行如何计算次数
计算次数
我们先把最大值抛掷脑后,我们先思考一下怎么计算出一个字符串分隔的次数,我们把每一个字符串都看成一个整体,那么分割之后,如果满足check函数的话,总的次数是不是就是等于分隔之后左字符串的分隔次数 + 右字符串的分隔次数,如图所示:
然后我们思考,怎么用代码表示这个状态呢,我们可以设置一个数组,
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示
i
i
i到
j
j
j分隔的次数,这样是不是就很清晰了,如图所示:
现在我们已经知道如何计算次数了,那么我们接下来思考,怎么计算最多的次数
最多次数求解
我们前面已经把 c h e c k check check判断,计算次数都讲解了,那么我们接下来进行最多次数求解,显然我们的最多次数是由多个子字符串组合而来的,那么我们就需要先进行枚举子字符串,计算出子字符串的次数,然后再进行状态转移,得到最终的次数,我们可以通过分别枚举字符串的长度,左端点和分隔点,来把所有字符串的分隔情况都计算,如下所示:
// 字符串长度
for(int len = 2; len <= n; len++)
{
// 左端点
for(int i = 0; i + len - 1 < n; i++)
{
// l是左端点,r是右端点
int l = i,r = l + len - 1;
// 分隔点
for(int k = l; k < r; k++){
}
}
}
上面我们枚举了所有的子字符串,接下来,我们需要计算出子字符串的最多分隔次数,前面有讲过,我们是不是可以通过计算次数,来得到当前字符串的分隔次数,那么我们也就是对一个字符串的所有计算次数取一个 m a x max max,就得到了一个字符串的最多次数,因为我们一个字符串的最多次数,他是包含在所有的次数计算中,那么状态转移如下:
// f[i][j]表示i到j分隔的最多次数
int cnt = abs((left[k] - left[l - 1]) - (right[r] - right[k]));
if(cnt >= L && cnt <= R)f[l][r] = std::max(f[l][r],f[l][k] + f[k + 1][r] + 1);
现在我们就把所有的思路都缕清了,那么我们把他们组合在一起,就是答案啦
最终代码
#include <iostream>
#include <algorithm>
#include <vector>
const int N = 5e2 + 10,MOD = 998244353;
int n,L,R;
int f[N][N]; //f[i][j]表示i到j分隔的最多次数
void solve()
{
std::cin >> n >> L >> R;
std::string s;
std::cin >> s;
std::vector<int> left(n + 10,0),right(n + 10,0);
for(int i = 0; i < s.size(); i++){
left[i] = left[i - 1] + (s[i] == '0');
right[i] = right[i - 1] + (s[i] == '1');
}
//for(int i = s.size() - 1; i >= 0; i--)
for(int len = 2; len <= n; len++)
{
for(int i = 0; i + len - 1 < n; i++)
{
int l = i,r = l + len - 1;
for(int k = l; k < r; k++){
int cnt = abs((left[k] - left[l - 1]) - (right[r] - right[k]));
if(cnt >= L && cnt <= R)f[l][r] = std::max(f[l][r],f[l][k] + f[k + 1][r] + 1);
}
}
}
std::cout << f[0][n-1];
}
int main()
{
int T = 1;//std::cin >> T;
while(T--) solve();
return 0;
}