【SHTSC2013】阶乘字符串
题意
判断前\(n\)个小写字母的全排列是否都在给定字符串\(S\)中作为子序列出现过。
\(n\leq 26,|S| \leq 450\)
解析
根据dalao的精确计算,当\(n>21\)时,\(C_{450}^{n}<n!\),所以\(n>21\)的情况可以直接输出"NO"。
对于一般的情况,设\(f_s\)表示字符串\(S\)中的一个位置,使得集合\(s\)中的字母的全排列都在\([1,f(s)]\)中出现过。此外还需要一个辅助数组\(next[i][j]\),表示字符串\(S\)中第\(i\)个位置后第一个字母\(j\)出现的位置(包括\(i\))。那么我们只需要枚举集合\(s\)的全排列是以哪个字母结尾的,就能转移了。
\[f_s=max_{i \in s}(next[f[s-bit(i)]][i])
\]
时间复杂度\(O(n*2^n)\),有了前面\(n\leq 21\)的保证,就可以顺利通过了。
Code
#include <cstdio>
#include <cstring>
const int N = 507;
int min(int a, int b) { return a < b ? a : b; }
int max(int a, int b) { return a > b ? a : b; }
int T, n, len, f[1 << 21];
int next[N][26];
char str[N];
int main()
{
scanf("%d", &T);
while (T--)
{
memset(next, 0x3f, sizeof(next));
memset(f, 0, sizeof(f));
scanf("%d", &n);
scanf("%s", str + 1);
if (n > 21) { printf("NO\n"); continue; }
len = strlen(str + 1);
for (int i = 1; i <= len; i++)
for (int j = 0; j < n; j++)
if (j == str[i] - 'a') next[i][j] = i;
else next[i][j] = len + 1;
for (int i = len; i >= 1; i--) for (int j = 0; j < n; j++) next[i][j] = min(next[i][j], next[i + 1][j]);
int full = (1 << n) - 1;
f[0] = 1;
for (int s = 1; s <= full; s++)
for (int i = 0; i < n; i++)
if ((1 << i) & s)
{
if (next[f[s - (1 << i)]][i] >= 0x3f3f3f3f) f[s] = len + 1;
else f[s] = max(f[s], next[f[s - (1 << i)]][i]);
}
if (f[full] <= len) printf("YES\n");
else printf("NO\n");
}
return 0;
}