湘南学院排位赛1-D

题目描述

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≤∣C0C1∣≤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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值