bzoj 4606: [Apio2008]DNA【dp】

写题五分钟读题两小时系列……
看懂题的话不算难,然而我去看了大佬的blog才看懂题……
题目大意是:一个原字符串,其中有一种通配符,合法串的定义是这个串(不含通配符))可以匹配原串并且这个串最多分成k段就能使每一段字典序单调不降。求在所有合法串中字典序第r大的。
设f[i][j][k]表示第i个字符选j,至少需要分成k段的方案数,倒着dp,特判一下通配符,比较基础就不多说了
然后对于每个f[i][j],把k维变成前缀和的形式,因为能分为k段就能分为k+1段。(这里其实当i<k的时候是不对的,但是我写的时候没有特判也过了就没改= =)
然后正着算答案,有点像splay上求k大数的感觉,就是一边匹配一边把r减去当前方案之前(字典序小于当前方案)的方案数。

#include<iostream>
#include<cstdio>
using namespace std;
const int N=50005;
int n,m,a[N],h[305];
long long r,ans,f[N][5][15];
char s[N],b[5];
int main()
{
	h['A']=0,h['C']=1,h['G']=2,h['T']=3,h['N']=4;
	b[0]='A',b[1]='C',b[2]='G',b[3]='T';
	scanf("%d%d%lld%s",&n,&m,&r,s+1);
	for(int i=1;i<=n;i++)
		a[i]=h[s[i]];
	if(a[n]==4)
		f[n][0][1]=1,f[n][1][1]=1,f[n][2][1]=1,f[n][3][1]=1;
	else
		f[n][a[n]][1]=1;
	for(int i=n-1;i>=1;i--)
	{
		if(a[i]==4)
		{
			for(int j=0;j<=3;j++)
				for(int k=1;k<=m;k++)
					for(int l=0;l<=3;l++)
						f[i][j][k]+=f[i+1][l][k-(j>l)];
		}
		else
		{
			for(int k=1;k<=m;k++)
					for(int l=0;l<=3;l++)
						f[i][a[i]][k]+=f[i+1][l][k-(a[i]>l)];
		}
	}
	for(int i=1;i<=n;i++)
		for(int j=0;j<=3;j++)
			for(int k=1;k<=m;k++)
				f[i][j][k]+=f[i][j][k-1];
	for(int i=1,j;i<=n;i++)
	{
		for(j=0;j<=3;j++)
		{
			long long sum;
			if(j<a[i-1])
				sum=f[i][j][m-1];
			else
				sum=f[i][j][m];
			if(r>sum)
				r-=sum;
			else
				break;
		}
		a[i]=j;
		if(a[i]<a[i-1])
			m--;
		printf("%c",b[j]);
	}
	return 0;
}
posted @ 2018-04-13 21:08  lokiii  阅读(137)  评论(0编辑  收藏  举报