Loading

【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;
}
posted @ 2019-07-02 16:27  gz-gary  阅读(268)  评论(0编辑  收藏  举报