CF1555D Say No to Palindromes题解--zhengjun
题目大意
给你一个字符串,每次询问区间 \([l,r]\) 最小需要改变几个才能使得这个区间的所有子区间都不是长度至少为 \(2\) 的回文串。
思路
首先我们想一想所有子区间都不是长度至少为 \(2\) 的回文串长什么样子。
- \(a_i\ne a_{i+1}\)
- \(a_i\ne a_{i+2}\)
这两个性质足够了。
所以这个串一定是形如 123123···
这样三个不同的字母组成的循环。
那么,对于每次询问,考虑枚举这个串的最终形式:
abcabc···
acbacb···
bacbac···
bcabca···
cabcab···
cbacba···
然后,要修改最少的步数变成这个目标串,可以看成算和这个目标串匹配的个数。
那么我们就可以把这三位分开处理,就只要询问一段区间中每隔三个是这个字符的数量就可以了,所以前缀和预处理递推一下就完事了。
最后要注意查询时区间前缀和左右端点是哪个减掉哪个就好了。
这个画个图就很直观的。
1 2 3 1 2 3 1 2 3
1 2 3 4 5 6 7 8 9
i j
1 2 3 1 2 3 1 2 3
1 2 3 4 5 6 7 8 9
i j
1 2 3 1 2 3 1 2 3
1 2 3 4 5 6 7 8 9
i j
具体细节见代码。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
void read(){}
template<typename _Tp,typename... _Tps>
void read(_Tp &x,_Tps &...Ar){
x=0;char c=getchar();bool flag=0;
while(c<'0'||c>'9')flag|=(c=='-'),c=getchar();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(flag)x=-x;
read(Ar...);
}
const int N=2e5+10;
int n,m,cnt[3][N],a[N];
char c[N];
int min(int a,int b,int c,int d,int e,int f){
if(b<a)a=b;
if(c<a)a=c;
if(d<a)a=d;
if(e<a)a=e;
if(f<a)a=f;
return a;
}
int solve(int l,int r,int x,int y,int z){
int k;x--;y--;z--;
if((r-l+1)%3==0){//对区间长度模 3 分类讨论
k=cnt[x][r-2]-cnt[x][l-3]+cnt[y][r-1]-cnt[y][l-2]+cnt[z][r]-cnt[z][l-1];//计算匹配数
}
else if((r-l+1)%3==1){
k=cnt[x][r]-cnt[x][l-3]+cnt[y][r-2]-cnt[y][l-2]+cnt[z][r-1]-cnt[z][l-1];
}
else{
k=cnt[x][r-1]-cnt[x][l-3]+cnt[y][r]-cnt[y][l-2]+cnt[z][r-2]-cnt[z][l-1];
}
return r-l+1-k;//修改的个数就是区间长度减去匹配的个数
}
int get(){
int i,l,r;
for(read(n,m),scanf("%s",c+1),i=1;i<=n;i++)a[i]=c[i]-'a';
for(i=1;i<=3;i++)cnt[a[i]][i]=1;
for(i=4;i<=n;i++)cnt[0][i]=cnt[0][i-3]+(a[i]==0),cnt[1][i]=cnt[1][i-3]+(a[i]==1),cnt[2][i]=cnt[2][i-3]+(a[i]==2);
while(m--){
read(l,r);
if(l==r)printf("0\n");//这里一定要特判掉
else printf("%d\n",min(solve(l,r,1,2,3),solve(l,r,1,3,2),solve(l,r,2,1,3),solve(l,r,2,3,1),solve(l,r,3,1,2),solve(l,r,3,2,1)));//枚举每一种情况
}
return 0;
}
int main(){
return get();
}