HYSBZ - 4416 阶乘字符串(字符串+状压DP)

https://vjudge.net/contest/369905#problem/J
题意:给一个n和一个字符串s,要求字母表前n个字符的n!个全排列,s都有子序列与它对应
解题思路:
dp[i][j]记录字符串中第i个位置之后最早出现的第j字母,注意初始化dp[len][…]=dp[len+1][…]=len+1; i位置后没有第j个字母的设置为len+1

for(int i=0;i<n;i++)
			dp[len][i]=len+1,dp[len+1][i]=len+1;
		for(int i=len;i>=1;i--)
		{
			for(int j=0;j<n;j++)
				if(j==s[i]-'a')
			       dp[i-1][j]=i;
				else
					dp[i-1][j]=dp[i][j];
		}

f[i]表示状态i全排列出现的最早位置,即比如状态为0101,表示的是ac和ca全部出现的最早位置

int length=(1<<n)-1;
		for(int i=1;i<=length;i++)
		{
			for(int j=0;j<n;j++)
				if(i&(1<<j))
					f[i]=max(f[i],dp[f[i^(1<<j)]][j]); 
					//i^(1<<j)表示第j个字母为最后一位的情况
					// f[i^(1<<j)]表示其出现的最早位置
					//dp[f[i^(1<<j)]][j]表示这个状态之后j最早出现的位置,也就是要求的f[i]的值
		}

另外当n>21时,直接判断情况不成立(怎么证明我也不会)

#include<cstdio>
#include<iostream>
#include<string.h>
using namespace std;
int t;
int n;
int len;
int f[1<<22];
int dp[500][30];
char s[500];
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		memset(f,0,sizeof(f));
		memset(dp,0,sizeof(dp));
		scanf("%d",&n);
		scanf("%s",s+1);
		if(n>21){
			printf("NO\n");
			continue;
		}
		len=strlen(s+1);
		for(int i=0;i<n;i++)
			dp[len][i]=len+1,dp[len+1][i]=len+1;
		for(int i=len;i>=1;i--)
		{
			for(int j=0;j<n;j++)
				if(j==s[i]-'a')
			       dp[i-1][j]=i;
				else
					dp[i-1][j]=dp[i][j];
		}
		int length=(1<<n)-1;
		for(int i=1;i<=length;i++)
		{
			for(int j=0;j<n;j++)
				if(i&(1<<j))
					f[i]=max(f[i],dp[f[i^(1<<j)]][j]);
		}
		if(f[length]==len+1)
			printf("NO\n");
		else
			printf("YES\n");
	}
	return 0;
}
原创文章 65 获赞 3 访问量 2084

猜你喜欢

转载自blog.csdn.net/littlegoldgold/article/details/105764925