2019年安徽省大学生程序设计竞赛 G.括号序列

题意:
给定 n , k n,k n,k,问第 k k k小的合法括号序列是什么。
你可以将左括号看成 0 0 0,右括号看成 1 1 1,求字典序第 k k k小的括号序列。
数据范围: 1 ≤ n ≤ 2000 , 1 ≤ k ≤ 1 0 18 1\leq n\leq 2000,1\leq k\leq 10^{18} 1n2000,1k1018

题解:
考虑 f [ i ] [ j ] f[i][j] f[i][j]为长度为 i i i的左括号个数减右括号个数为 j j j的合法括号序列方案数,保证左括号个数不小于右括号个数。
那么状态转移方程为: f [ i ] [ j ] = f [ i − 1 ] [ j + 1 ] + f [ i − 1 ] [ j − 1 ] f[i][j] = f[i-1][j+1]+f[i-1][j-1] f[i][j]=f[i1][j+1]+f[i1][j1],注意 f [ i ] [ j ] f[i][j] f[i][j]只需最多到 k k k即可。

之后从前往后枚举每个位置应该放置的括号。
当前枚举到位置 i i i,左括号个数减右括号个数为 n o w now now
先看是否可以放置左括号,条件为放置左括号的总方案数不比 k k k小,
f [ n − i ] [ n o w + 1 ] ≥ k f[n-i][now+1]\geq k f[ni][now+1]k,则放置左括号即可, n o w now now 1 1 1
否则放置右括号,需要减去该位置放置左括号的所有方案 f [ n − 1 ] [ n o w + 1 ] f[n-1][now+1] f[n1][now+1] n o w now now 1 1 1

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long ll;

const int N = 2010;
const ll MAX = 1e18;
ll f[N][N]; //f[i][j]长为i,j为cnt0 - cnt1的方案数 
char ans[N];
int n;
ll k;

int main()
{
    
    
	scanf("%d%lld", &n, &k);
	
	memset(f, 0, sizeof f);
	f[0][0] = 1;
	for(int i = 1; i <= n; ++i)
		for(int j = 0; j <= i; ++j) {
    
    
			//1. i - 1, j + 1
			if(i - 1 >= j + 1) f[i][j] += f[i - 1][j + 1];
			//2. i - 1, j - 1
			if(j - 1 >= 0) f[i][j] += f[i - 1][j - 1];
			f[i][j] = min(f[i][j], MAX); 
		}
		
	int now = 0;
	for(int i = 1; i <= n; ++i) {
    
    
		//由于左括号越靠前则该括号序列的值越小,故优先看能不能放左括号 
		//如果该位置放置左括号的方案小于k,就需要放置右括号,并且把左括号的方案删除。 
		if(f[n - i][now + 1] >= k) ans[i] = '(', ++now;
		else ans[i] = ')', k -= f[n - i][now + 1], --now;
	}
	ans[n + 1] = '\0';
	puts(ans + 1);
	
	return 0;
}

Guess you like

Origin blog.csdn.net/weixin_43900869/article/details/115545520