【动态规划】北航程序设计竞赛决赛B题

 决赛那天感觉确实没发挥好。本来有机会逃离三题大队的。结果中途很多人搞了几何构造题,忍不住跟风。事实证明以我的想象力果然搞不出来。还不如专心搞搞B这题。
 B的题意很简单,给一个长度不超过5000的字符串,字符集大小不超过5000,求字典序第k小的子串(子串可以不连续),k规模不超过子串总数以及1E18。
 先考虑每个字符都不同的情况,很容易想到一个一个把答案的字符定下来。我们每一次求出下一步接字符a[i]的子串个数,也就是 2 n − i 2^{n-i} 2ni,那么从小到大枚举字符值x,找到第k个字符串所在的那个x即可,再让k减去这一步小于x的子串总数,进入下一步。
 出现重复字符时, 2 n − i 2^{n-i} 2ni还需要乘上一个累计值g,这个值取决于a[i]之前出现的已有字符串ans的总数。用dp来记录以i结尾的字符串ans总数,这个累计值就是dp数组的前缀和。注意如果g[n]>=k则下一个字符是空的,直接退出就好了。
 由于题目没有放在OJ上,以下代码只是随便打打,题解的解释之后也会进一步补充。
 自己以后思路还需要放清晰,不能在比赛的时候慌乱。

#include
#include
using namespace std;
using LL=long long;

int T,n,m,k,a[5005],ans[5005];
LL dp[5005],g[5005],p2[5005],cnt[5005];

int main()
{
	scanf("%d",&T);
	p2[0]=1;
	for(int i=1;i<=5000;i++)
		p2[i]=min(p2[i-1]*2,(LL)1E18);
	while(T--)
	{
		scanf("%d%d%d",&n,&m,&k);
		g[0]=1;
		for(int i=1;i<=n;i++)
			scanf("%d",&a[i]),g[i]=1,dp[i]=0;
		for(int i=1;;i++)
		{
			for(int j=1;j<=m;j++)
				cnt[j]=0;
			for(int j=1;j<=n;j++)
				cnt[a[j]]=min(cnt[a[j]]+(LL)p2[n-j]*min((LL)1E18/p2[n-j],g[j-1]),(LL)1E18);
			int pos=0;
			LL sum=0;
			for(;sum+cnt[pos]<k;pos++)
				sum+=cnt[pos];
			k-=sum;
			ans[i]=pos;
			LL tmp=i==1;
			for(int j=1,t;j<=n;j++)
			{
				t=dp[j];
				dp[j]=0;
				if(a[j]==pos)
					dp[j]=tmp;
				tmp=min(tmp+t,(LL)1E18);
			}
			g[0]=0;
			for(int j=1;j<=n;j++)
				g[j]=min(dp[j]+g[j-1],(LL)1E18);
			if(k<=g[n])
			{
				for(int j=1;j<=i;j++)
					printf("%d ",ans[j]);
				puts("");
				break;
			}
			k-=g[n];
		}
	}
	return 0;
}

你可能感兴趣的:(动态规划/递推)