1. 程式人生 > 其它 >2021牛客OI賽前集訓營-提高組(第五場)C-第K排列【dp】

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;
}