题目链接:https://ac.nowcoder.com/acm/contest/217/B
本题解参考:https://blog.youkuaiyun.com/Jaihk662/article/details/83903786
题意:给出长为n的仅由'm','s','c'组成的字符串str[],问其中有多少区间[x,y]使得str[x,y]包含两个互不重叠的子序列'msc'和'mcc'。
解析:打表可以发现满足条件的区间[x,y]只需包含以下8种子序列:
mmsccc、mmcscc、mmccsc、msmccc、mscmcc、mcmscc、mcmcsc、mccmsc
考虑只需枚举每个区间起点x,求一个最小的y,使得[x,y]是最短合法区间,那么起点x对答案的贡献就是n-y+1;如果对于x找不到合法y,那么它对答案贡献为0。考虑对于字符串str[]先求出net[][]数组:net[j][i]是str[j]后面字符i的最近位置('a'≤i≤'z')。那么枚举每个区间端点x时,先嵌套第二层循环枚举每一个合法序列,再嵌套第三层循环用next数组找最近的y,即可得到一个O(n*8*6)的算法。
代码(37ms):
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
char str[MAXN];
char temp[20][20]={
"mmsccc",
"mmcscc",
"mmccsc",
"msmccc",
"mscmcc",
"mcmscc",
"mcmcsc",
"mccmsc"
};
int n,net[MAXN][28];
int main()
{
scanf("%d",&n);
scanf("%s",str+1);
for(int i='a';i<='z';i++)
{
for(int j=n;j>=1;j--)
{
net[j-1][i-'a']=net[j][i-'a'];//net[j][i]是a[j]后面字符i的最近位置
if(str[j]==(char)i)
net[j-1][i-'a']=j;
}
}
long long ans=0;
for(int x=1;x<=n;x++)//枚举区间起点为i,寻找最小的y
{
int y=n+1;
for(int i=0;i<8;i++)//每种合法字符串,其最早结束位置为now
{
int now=x-1;
for(int j=0;j<6;j++)//合法的字符串长度为6
{
now=net[now][temp[i][j]-'a'];
if(now==0) break;
}
if(now!=0) y=min(y,now);
}
ans+=n-y+1;
}
printf("%lld\n",ans);
return 0;
}
打表代码:
#include<bits/stdc++.h>
using namespace std;
string str[800];//所有可能序列
string ans[800];//所有合法序列
int num,len;
void dfs(int step,string tmp)
{
if(step>6) {str[++num]=tmp;return;}
dfs(step+1,tmp+'m');
dfs(step+1,tmp+'s');
dfs(step+1,tmp+'c');
}
bool ok(int index)
{
bool flag=false;
for(int i=0;i<6;i++)
{
for(int j=i+1;j<6;j++)
{
for(int k=j+1;k<6;k++)
{
string tmp="";
tmp+=str[index][i];
tmp+=str[index][j];
tmp+=str[index][k];
if(tmp=="msc")
{
tmp="";
for(int l=0;l<6;l++)
{
if(l!=i&&l!=j&&l!=k) tmp+=str[index][l];
}
if(tmp=="mcc") {flag=true;break;}
}
}
}
}
return flag;
}
int main()
{
dfs(1,"");
for(int i=1;i<=num;i++)
{
if(ok(i)) ans[++len]=str[i];
}
for(int i=1;i<=len;i++)
cout<<ans[i]<<endl;
return 0;
}
/*
mmsccc
mmcscc
mmccsc
msmccc
mscmcc
mcmscc
mcmcsc
mccmsc
*/