2021牛客OI赛前集训营-提高组(第五场)C-第K排列【dp】

正题

题目链接:https://ac.nowcoder.com/acm/contest/20110/C


题目大意

一个长度为\(n\)的字符串\(S\),\(S\)中存在一些\(?\),有\(N/O/I/P\)四个字符作为字符集,每对相邻的字符会产生不同的贡献,现在要求所有权值不小于\(x\)的字符串中字典序第\(k\)大的。

\(1\leq n,k\leq 1000,1\leq x\leq 10^9\)


解题思路

考虑到暴力\(dfs\)搜索的瓶颈在于我们可能会搜到大量权值小于\(x\)的序列,所以如果保证我们每次都能搜到满足条件的字符我们就可以\(O(nk)\)解决这个问题。

可以设\(f_{i,j}\)表示\(i\)个字符是\(j\)开始往后最多能得到多少权值。

然后根据\(dp\)数组暴力\(dfs\)就好了。

时间复杂度:\(O(nk)\)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=1100;
ll n,l,k,w[4][4],f[N][4],a[N],c[N];
char s[N];
void dfs(ll x,ll p,ll sum){
	if((c[x]!=-1&&c[x]!=p)||sum+f[x][p]<l||!k)return;
	a[x]=p;
	if(x==n&&sum>=l){
		k--;
		if(!k){
			for(ll i=1;i<=n;i++){
				if(a[i]==0)putchar('N');
				if(a[i]==1)putchar('O');
				if(a[i]==2)putchar('I');
				if(a[i]==3)putchar('P');
			}
			putchar('\n');
		}
		return;
	}
	dfs(x+1,3,sum+w[p][3]);
	dfs(x+1,1,sum+w[p][1]);
	dfs(x+1,0,sum+w[p][0]);
	dfs(x+1,2,sum+w[p][2]);
	return;
}
signed main()
{
	scanf("%lld%lld%lld",&n,&l,&k);
	scanf("%s",s+1);
	for(ll i=1;i<=n;i++){
		if(s[i]=='N')c[i]=0;
		if(s[i]=='O')c[i]=1;
		if(s[i]=='I')c[i]=2;
		if(s[i]=='P')c[i]=3;
		if(s[i]=='?')c[i]=-1;
	}
	for(ll i=0;i<4;i++)
		for(ll j=0;j<4;j++)
			scanf("%lld",&w[i][j]);
	memset(f,0xcf,sizeof(f));
	for(ll i=0;i<4;i++)
		if(c[n]==-1||c[n]==i)f[n][i]=0;
	for(ll i=n-1;i>=1;i--)
		for(ll j=0;j<4;j++){
			if(!(c[i]==-1||c[i]==j))continue;
			for(ll k=0;k<4;k++)
				f[i][j]=max(f[i][j],f[i+1][k]+w[j][k]);
		}
	dfs(1,3,0);dfs(1,1,0);dfs(1,0,0);dfs(1,2,0);
	if(k)puts("-1");
	return 0;
}
上一篇:简单的公式


下一篇:P3639-[APIO2013]道路费用【最小生成树】