问题链接(https://codeforces.com/problemset/problem/1196/D1)
问题链接(https://codeforces.com/problemset/problem/1196/D2)
问题描述
有长为n的字符串,要求通过修改一些字符得到一个长度为k的子串,使得这个子串是"RGBRGB…"的子串。输出修改的字符的最小个数。D1与D2的区别是n,k的大小不一样。
问题分析
D1做法:暴力遍历。因为规模较小,所以通过选择不同开头(所给字符的开头和“RGB”的开头),计数来得到每个情况得到的k长子串与无限长度"RGB"的相同位置上字符相同的个数,越大越好。最后取最大的与k相减就是答案。值得注意的是,"RGB…"的开头只有3种。
D2做法:前缀和降维。由于规模变大了,之前的做法会超时。但我们可以发现,我们重复计算了很多东西,尤其是一个个求每个子串的字符相同的个数。这些重复的计算可以通过用前缀和处理掉。
由于"RGB…“的子串只有三种开头,所以匹配的时候要分成3种情况,分别以’R’、‘G’、'B’开头的”…RGB…"对原串的匹配,也就是下表的1、2、3原串的错位对齐。由于原串的开头也要变化,所以先求{x1,x2,x3,…,xk}与“RGB…”的字符匹配个数,按D1的做法接着是求{x2,x3,x4,…,x(k+1)}与“RGB…”的匹配个数,可以发现,这个问题其实在情况3的时候会再处理一次,所以为了减少重复的计算,在情况1就不进行以x2、R开头的计算了,相同的其它这样的问题都会在情况1、2、3中穿插,进行一次就好了。其次是,利用前缀和快速找到(即不进行过多的中间计算)每个情况中每个长度为k的子串的匹配情况,虽然也是求和,但这一步就是减少时间花费的最重要一步。
R | G | B | R | G | B | R | G | |
---|---|---|---|---|---|---|---|---|
1 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 |
2 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | |
3 | x1 | x2 | x3 | x4 | x5 | x6 |
代码如下
//D1
#include<bits/stdc++.h>
using namespace std;
const string rgb="RGB";
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
string str;
int q,i,j,k,n,t,ti,tj,maxn,temp;
cin>>q;
while(q--){
cin>>n>>k>>str;
maxn=0;
for(i=0;i<3;i++){
for(j=0;j<n;j++){
if(j+k>n) break;
temp=0;
ti=i,tj=j;//设置开头
for(t=0;t<k;t++){
if(ti==3) ti=0;
if(rgb[ti++]==str[tj++]) temp++;
}
maxn=max(maxn,temp);
}
}
cout<<k-maxn<<endl;
}
return 0;
}
//D2
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
const string rgb="RGB";
int sum[N];
char s[N];
int main(){
string str;
int q,i,j,k,n,t,ti,tj,maxn,temp;
scanf("%d",&q);
while(q--){
scanf("%d %d %s",&n,&k,s);
str=s;
maxn=0;
for(i=0;i<3;i++){
sum[0]=0;
ti=i;
for(j=0;j<n;j++,ti++) sum[j+1]=sum[j]+(rgb[ti%3]==str[j]?1:0);//得到前缀和数组
int m=n-k+1;//j+k>n+1 break
for(j=0;j<=m;j++) maxn=max(maxn,sum[j+k]-sum[j]);//遍历找到字符最大匹配数
}
printf("%d\n",k-maxn);
}
return 0;
}
#endif