目录
题目
题目描述
jackle 在校赛的时候出过一道 "切割 01 串" 的题目,如今他又出了一道切割 01 串的题目:
给定一个长度为 n 的 01 串,定义如下操作为一次 "切割":
- 将长度大于 1的字符串分割为两个非空的连续字串,记分割出来的左侧字串 a 中 0 的出现次数为 C0,右侧字串 b 中 1 出现的次数为 C1,需要满足 L≤∣C0−C1∣≤R 。
你每次切割完,都会得到两个新 01串,你可以继续选择这些已经被你切出来的 01 串做切割,只要满足切割条件。
jackle 想问你最多可以切割多少次?
输入描述
第一行输入 333 个整数,n (1≤n≤500),L,R (0≤L≤R≤500),分别表示字符串长度,和题目中的两个参数。 第二行输入 1 个长度为 n 的 01 串。
输出描述
输出最多能切割多少次?
输入
6 2 3
011011
输出
3
样例说明
其中一种切割次数最多的切法如下:
第一次切割可以切:0 ∣ 11011,然后选择 11011 这个串继续做切割。
第二次切割可以切:1 ∣ 1011,然后选择 1011这个串继续做切割。
第三次切割可以切:1 ∣ 011。
解题思路(区间DP)
设 表示从 到 这个串中最多可以切割的次数,我们要求解的答案就是 。
对于 到 这个子串,他的最大切割次数可以由他切割后的两个子串得到,假设在 到 这个子串,我们从处切割,如果在此处切割是一种可行的方案
可得转移方程:
显而易见,我们要枚举 的位置来确定最优的切割位置,求得最优解。
枚举子串,第一层枚举字串长度,然后枚举左端点 ,进而确定右端点 。
枚举左端点 到右端点 中可分割的位置 。利用动态转移方程求解。
完整代码(C++)
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=510;
int cnt0[N],cnt1[N];//cnt0[i]、cnt1[i]:前i个字符中0、1的个数
int dp[N][N];//dp[i][j]表示从i到j这个串中最多可以切割的次数
void solve(){
int n,l,r;
cin>>n>>l>>r;
string s;cin>>s;
s=" "+s;
//前缀和处理一下0和1的个数
for(int i=1;i<=n;i++){
cnt0[i]=cnt0[i-1]+(s[i]=='0'?1:0);
cnt1[i]=cnt1[i-1]+(s[i]=='1'?1:0);
}
//枚举字串长度
for(int len=1;len<=n;len++){
//枚举左端点
for(int i=1;i<n;i++){
//确定右端点
int j=i+len-1;
if(j>n)break;
//枚举切割位置
for(int k=i;k<j;k++){
int C0 = cnt0[k] - cnt0[i - 1]; //i到k中0的个数
int C1 = cnt1[j] - cnt1[k]; //k+1到j中1的个数
if (abs(C0 - C1) >= l && abs(C0 - C1) <= r)
//符合切割条件,利用动态转移方程求解
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k+1][j] + 1);
}
}
}
//1到n区间中最多可以切割的次数
cout<<dp[1][n]<<endl;
}
signed main(){
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}