【SHTSC2013】阶乘字符串

题意

判断前(n)个小写字母的全排列是否都在给定字符串(S)中作为子序列出现过。
(nleq 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)),有了前面(nleq 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
"); 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
");
		else printf("NO
");
	}
	return 0;
}
原文地址:https://www.cnblogs.com/zjlcnblogs/p/11121421.html