Description&Data Constraint
给定一个由前n个小写字母组成的串S。
串S是阶乘字符串当且仅当前n个小写字母的全排列(共n!种)都作为S的子序列(可以不连续)出现。
由这个定义出发,可以得到一个简单的枚举法去验证,但是它实在太慢了。所以现在请你设计一个算法,在1秒内判断出给定的串是否是阶乘字符串。
Solution
首先根据数据范围可以发现当 n > 21 n>21 n>21的时候是没有解的,详情请见:
预处理 n x t i , j nxt_{i,j} nxti,j表示 s s s中 i i i之后的第一个 j j j的位置
设 f i f_i fi表示 i i i中的字母在 s s s中存在全排列的最小下标,那么每次加入新的字母 j j j,可以得出转移方程
f i ∣ ( 1 < < j ) = max ( n x t f i , j ) f_{i|(1<<j)}=\max(nxt_{f_i,j}) fi∣(1<<j)=max(nxtfi,j)
取最大值是为了使得全排列都有的选择
Code
#include<cstdio>
#include<cstring>
#include<iostream>
#define L 455
#define N 30
#define inf 0X3f3f3f3f
using namespace std;
int t,n,m,a[N],f[4000000],nxt[L][N];
char s[L];
int main()
{
scanf("%d",&t);
while (t--)
{
scanf("%d%s",&n,s+1);
m=strlen(s+1);
if (n>21)
{
printf("NO\n");
continue;
}
for (int i=0;i<n;++i)
nxt[m][i]=inf;
for (int i=m;i;--i)
{
for (int j=0;j<n;++j)
nxt[i-1][j]=nxt[i][j];
nxt[i-1][s[i]-'a']=i;
}
memset(f,0,sizeof(f));
for (int s=0;s<(1<<n);++s)
{
if (f[s]==inf)
{
f[(1<<n)-1]=inf;
break;
}
for (int i=0;i<n;++i)
{
if ((s|(1<<i))>s)
{
if (f[s]==inf) f[s|(1<<i)]=inf;
else f[s|(1<<i)]=max(f[s|1<<i],nxt[f[s]][i]);
}
}
}
if (f[(1<<n)-1]!=inf) printf("YES\n");
else printf("NO\n");
}
return 0;
}