CodeforcesD2. RGB Substring (hard version)

本文介绍了一种优化算法,用于解决RGB序列的替换问题。通过枚举三种可能的起始颜色,利用前缀和思想计算最小子串的替换次数,避免了传统方法的效率瓶颈。该算法适用于需要频繁计算字符串相似度的场景。

题目入口

解法:

这个题还有一个easy版本的。

1. 首先应该知道怎么计算替换次数:因为要保持顺序,所以只要将长度相同的两个串进行对应位置一一对比,不相同的就计数,之后比较计数最小的就行了。

2. 拿什么比较?因为是RGBGB...依次循环下去的,所以我们只要枚举开头的字母,一共是三种情况:RGB.../GBR.../BRG...,即可。

3. 优化:如果每次将原串中长度为K的子串挑选出来进行比较,则这种做法比较低效,TLE在所难免。这里可以忽略K值,我们利用前缀和的思想,sum[i][j]代表第i中情况(前面说的三种开头字母的情况),j表示字符串S中前j个字符需要调整的次数。如何迭代sum参照代码。

AC Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define FAST_IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
#define INF 0x3f3f3f3f

const int maxn=1e6+9;

int q,n,k,ans,sum[3][maxn];
string s,t="RGB";

int main()
{
    scanf("%d",&q);
    while(q--)
    {
        ans=INF;
        scanf("%d%d",&n,&k);
        cin>>s;

        sum[0][0]=sum[1][0]=sum[2][0]=0;

        for(int d=0;d<3;d++)
            for(int i=1;i<=n;i++)
                if(s[i-1]!=t[(d+i-1)%3])
                    sum[d][i]=sum[d][i-1]+1;
                else
                    sum[d][i]=sum[d][i-1];

        for(int d=0;d<3;d++)
            for(int i=k;i<=n;i++)
                ans=min(ans,sum[d][i]-sum[d][i-k]);
        printf("%d\n",ans);
    }
    return 0;
}

 

<think> 我们正在讨论的是字符串的Substring方法。根据引用[2]和[3],我们可以知道Substring方法用于提取字符串的子串。在Java中,`str.substring(beginIndex, endIndex)`返回从`beginIndex`开始到`endIndex-1`结束的子字符串。注意,索引从0开始,且包含`beginIndex`处的字符,不包含`endIndex`处的字符。 用户询问:`str.Substring(40, 2)`的用法和作用。 但是,请注意,在Java中,`substring`方法的第二个参数是结束索引(不包含),因此要求`beginIndex`必须小于等于`endIndex`,且两者在字符串的索引范围内(0到字符串长度-1)。另外,索引值不能为负数。 然而,这里提供的参数是(40, 2): - 第一个参数是40(起始索引) - 第二个参数是2(结束索引) 因为40>2,这会导致`StringIndexOutOfBoundsException`异常,因为起始索引不能大于结束索引。 所以,这样的用法是错误的。 正确的用法应该是:第一个参数小于等于第二个参数,且两个参数都在合理的范围内(0到字符串长度-1,且结束索引最大可以为字符串长度,即到字符串末尾的下一个位置)。 例如,如果要从索引2开始提取两个字符,应该是`str.substring(2, 4)`,因为结束索引是起始索引+要提取的字符数。 因此,`str.Substring(40, 2)`这样的调用会抛出异常。 但是,请注意,在C#中,Substring方法有两种重载: 1. `Substring(int startIndex)`: 从startIndex开始到字符串末尾。 2. `Substring(int startIndex, int length)`: 从startIndex开始,截取长度为length的子串。 在C#中,`str.Substring(40, 2)`表示从索引40开始,截取2个字符。这要求字符串的长度至少为42(因为40+2=42),否则会抛出`ArgumentOutOfRangeException`。 所以,我们需要明确用户指的是哪种语言。根据引用内容,引用[1][2][3]都是关于Java的,但引用[4]使用了JavaScript(虽然方法名是substring,但JavaScript中也是使用起始索引和结束索引,不过参数顺序和Java一样,即`substring(startIndex, endIndex)`,而且不允许startIndex>endIndex,如果出现会交换两个参数?实际上,在JavaScript中,如果第一个参数大于第二个参数,则会在提取前交换两个参数,使得较小的作为起始索引,较大的作为结束索引。但是,在Java中,如果startIndex>endIndex,会直接抛出异常。 然而,用户的问题中写的是`str.Substring(40,2)`,并且使用了首字母大写的`Substring`(在Java中方法是`substring`,首字母小写;在C#中方法是`Substring`,首字母大写)。因此,我们推断用户可能指的是C#,因为C#中方法名为`Substring`且首字母大写,并且第二个参数表示长度。 所以,我们分两种情况讨论: 1. **Java语言**:`str.substring(40, 2)`会抛出`StringIndexOutOfBoundsException`,因为40>2,不符合要求(要求0<=beginIndex<=endIndex<=字符串长度)。 2. **C#语言**:`str.Substring(40, 2)`表示从索引40开始,截取长度为2的子字符串。要求字符串的长度至少为42(索引从0开始,所以索引40是第41个字符,需要42个字符才能有40和41两个字符)。如果字符串长度不足,会抛出`ArgumentOutOfRangeException`。 3. **JavaScript语言**:`str.substring(40, 2)`,因为40>2,所以方法内部会交换这两个参数,相当于`str.substring(2,40)`,即从索引2到索引39(不包括40)的子串。如果字符串长度小于40,则从2到字符串末尾。 由于用户的问题中方法名首字母大写(Substring),我们倾向于C#。但是,我们也要指出其他语言中的情况。 因此,回答如下: ### `str.Substring(40, 2)`的用法和作用 在不同的编程语言中,`Substring`(或`substring`)方法的参数含义可能不同,因此需要根据语言来区分: #### 1. C# 中的 `Substring` 方法 在C#中,`Substring`方法有两种重载形式: ```csharp public string Substring(int startIndex); public string Substring(int startIndex, int length); ``` - `str.Substring(40, 2)`:使用第二个重载,表示从索引位置`40`开始,截取长度为`2`的子字符串。 - **要求**:字符串的长度必须大于等于`40+2=42`,否则会抛出`ArgumentOutOfRangeException`。 - **示例**: ```csharp string str = "This is a sample string that has at least 42 characters..."; string sub = str.Substring(40, 2); // 返回从索引40开始的2个字符 ``` #### 2. Java 中的 `substring` 方法 在Java中,`substring`方法也有两种形式,但参数含义不同: ```java public String substring(int beginIndex) public String substring(int beginIndex, int endIndex) ``` - `str.substring(40, 2)`:使用第二个重载,但这里`beginIndex=40`大于`endIndex=2`,这违反了方法的要求(`beginIndex`应小于等于`endIndex`),因此会抛出`StringIndexOutOfBoundsException`。 - **正确用法**:参数应该是`(beginIndex, endIndex)`,且`0<=beginIndex<=endIndex<=字符串长度`。 #### 3. JavaScript 中的 `substring` 方法 在JavaScript中,`substring`方法的参数与Java类似: ```javascript str.substring(startIndex, endIndex) ``` - `str.substring(40, 2)`:由于`startIndex=40`大于`endIndex=2`,方法内部会自动交换这两个参数,相当于执行`str.substring(2,40)`,即返回从索引2到索引39的子字符串(不包含索引40)。 - **注意**:如果字符串长度小于40,则返回从索引2到字符串末尾的子串。 ### 总结 - 在C#中,`str.Substring(40, 2)`是合法的,表示从索引40开始截取2个字符。 - 在Java中,`str.substring(40, 2)`会抛出异常,因为起始索引大于结束索引。 - 在JavaScript中,`str.substring(40,2)`会被自动转换为`str.substring(2,40)`,返回索引2到39的子串。 因此,使用该方法时,请确保: 1. 明确所用编程语言。 2. 了解该语言中`Substring`(或`substring`)方法的参数定义。 3. 确保参数在有效范围内,避免异常。 §§相关问题§§ 1. 在C#中如何避免`Substring`方法抛出`ArgumentOutOfRangeException`? 2. Java的`substring`方法和C#的`Substring`方法有哪些区别? 3. 在JavaScript中,`substring`和`substr`方法有什么不同? </think>### `str.Substring(40, 2)` 的用法和作用详解 在字符串处理中,`Substring` 方法用于提取字符串的指定部分。针对 `str.Substring(40, 2)` 的用法: #### 1. **方法定义** ```csharp public string Substring(int startIndex, int length) ``` - **`startIndex`**:子字符串的起始位置(基于0的索引) - **`length`**:要提取的字符数 - **返回值**:从 `startIndex` 开始、长度为 `length` 的新字符串 #### 2. **具体作用** `str.Substring(40, 2)` 表示: - 从字符串 `str` 的第 **41个字符** 开始(索引40对应第41个字符) - 提取连续 **2个字符** - 示例: ```csharp string str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()"; string result = str.Substring(40, 2); // 结果: "()" (索引40='(',索引41=')') ``` #### 3. **关键注意事项** 1. **索引范围验证**: - 若 `str.Length < 42` 会抛出 `ArgumentOutOfRangeException` - 安全用法: ```csharp if (str.Length >= 42) { string part = str.Substring(40, 2); } ``` 2. **与其他语言的差异**: - **Java**:`str.substring(40, 42)`(结束索引需明确指定)[^1][^2] - **JavaScript**:`str.substring(40, 42)`(参数为起止索引) 3. **常见应用场景**: - 解析固定格式数据(如通信协议、日期、编码等) ```csharp // 解析日期 "20231015" string date = "20231015"; int month = int.Parse(date.Substring(4, 2)); // 得到 10[^3] ``` - 截取末尾字符(结合长度计算) ```csharp // 移除最后1个字符(替代 str[..^1]) string result = str.Substring(0, str.Length - 1); [^4] ``` #### 4. **错误处理示例** ```csharp try { string data = str.Substring(40, 2); } catch (ArgumentOutOfRangeException ex) { // 处理长度不足的情况 Console.WriteLine($"字符串长度不足: {ex.Message}"); } ``` #### 5. **性能建议** - 避免在循环中反复调用(尤其是长字符串) - 需要截取多段时,优先使用 `Span<T>`: ```csharp ReadOnlySpan<char> span = str.AsSpan(); var segment = span.Slice(40, 2); // 零内存分配 ``` ### 与其他方法的比较 | 方法 | 示例 | 特点 | |------|------|------| | `Substring(start, length)` | `str.Substring(40,2)` | 明确长度,适合定长截取 | | `Substring(start)` | `str.Substring(40)` | 截取到字符串末尾 | | `AsSpan().Slice()` | `span.Slice(40,2)` | 高性能,无内存分配 | | 正则表达式 | `Regex.Match(str, ".{40}(..)")` | 适合非固定位置匹配 | > 当需要精确截取固定位置和长度的子串时(如协议解析[^3]),`Substring(startIndex, length)` 是最简洁高效的解决方案。但在处理变长数据时,正则表达式或 `IndexOf` 更灵活[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值