天天看点

Atcoder Grand Contest 030 F - Permutation and Minimum(DP)

计数 dp,思维好题

洛谷题面传送门 & Atcoder 题面传送门

12 天以前做的题了,到现在才补/yun

做了一晚上+一早上终于 AC 了,写篇题解纪念一下

首先考虑如果全是 \(-1\)​ 怎么处理。由于我们不关心每个

pair

中的

max

是多少,并且显然每个

pair

的最小值都是两两不同的,因此我们可以考虑有多少个最小值组成的集合,然后答案乘上 \(n!\) 即可。而显然如果我们将“作为某个

pair

的最小值”的位置放上一个左括号,“不作为某个

pair

的最小值”的位置放上一个右括号,那么一个集合符合条件当且仅当这个集合对应的括号序列是一个合法括号序列,卡特兰数算算即可。

接下来思考存在某些数固定下来的情况怎么处理,显然我们可以将所有

pair

分为三类:\((-1,-1),(-1,x),(x,y)\),第三类的最小值显然是固定的,我们可以将这样的 \((x,y)\) 中的 \(x,y\) 设为访问过,然后只考虑那些没有访问过的元素。显然带上“某些值固定”这个限制条件后就无法直接套用组合数了,并且显然这个数据范围也不是让你纯组合数学就过的,因此考虑一个 DP。受到上面括号序列这个思想的启发,我们考虑这样一个过程:

  1. 我们考虑有一个长度为 \(m\) 的括号序列,你要在里面填上 \(m/2\) 个左括号和 \(m/2\)​ 个右括号,其中 \(m=2n-2\times\text{两个位置都已经确定了的 pair 的对数}\)
  2. 括号有方括号和圆括号之分,给定 \(k\)​ 个位置,填上的括号必须为方括号,另外 \(m-k\) 个位置填上的括号必须为圆括号。
  3. 填好左右括号之后,你要对于所有右方括号,在前面找一个左圆括号匹配,并且满足去掉这些已经匹配过的括号之后,剩下的括号在不分方括号和圆括号的情况下仍能组成一个合法括号序列
两个方案不同当且仅当括号序列不同或者存在某个右方括号,满足与其匹配的左圆括号位置不同。

够清晰的了吧

  • 如果它只能填方括号:
    • 如果填左括号,那么它只能与圆括号匹配,\(dp_{i,j,k}\to dp_{i-1,j-1,k}\)
    • 如果填右括号,那么未匹配的 ‘)’ 个数加一,\(dp_{i,j,k}\to dp_{i-1,j+1,k}\)
  • 如果它只能填圆括号:
    • 如果填左括号,那么有两种可能,与圆括号匹配,由于这种情况是不在乎它与哪个圆括号匹配的,因此贡献只有 \(1\),即 \(dp_{i,j,k}\to dp_{i-1,j-1,k}\),或者它也可以与方括号匹配,显然每个左圆括号与哪个右方括号匹配都是不同的,因此有 \(k\) 种可能,\(dp_{i,j,k}·k\to dp_{i-1,j,k-1}\)
    • 如果填右括号,那么未匹配的 ‘)’ 个数加一,\(dp_{i,j,k}\to dp_{i-1,j,k+1}\)
const int MAXN=600;
const int MOD=1e9+7;
int n,a[MAXN+5],vis[MAXN+5],id[MAXN+5],m=0,is[MAXN+5];
int dp[MAXN+5][MAXN+5][MAXN+5];
void add(int &x,int v){((x+=v)>=MOD)&&(x-=MOD);}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n<<1;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n<<1;i+=2) if(~a[i]&&~a[i+1]) vis[a[i]]=vis[a[i+1]]=1;
	for(int i=1;i<=n<<1;i++) if(!vis[i]) id[i]=++m;
	for(int i=1;i<=n<<1;i+=2){
		if(~a[i]&&!~a[i+1]) is[id[a[i]]]=1;
		if(!~a[i]&&~a[i+1]) is[id[a[i+1]]]=1;
	} dp[m+1][0][0]=1;
//	for(int i=1;i<=m;i++) printf("%d\n",is[i]);
	for(int i=m;i;i--) for(int j=0;j<=m-i;j++) for(int k=0;k+j<=m-i;k++){
		if(is[i]){
			add(dp[i][j][k+1],dp[i+1][j][k]);
			if(j) add(dp[i][j-1][k],dp[i+1][j][k]);
		} else {
			add(dp[i][j+1][k],dp[i+1][j][k]);
			if(k) add(dp[i][j][k-1],1ll*dp[i+1][j][k]*k%MOD);
			if(j) add(dp[i][j-1][k],dp[i+1][j][k]);
		} //printf("%d %d %d %d\n",i,j,k,dp[i][j][k]);
	} int res=dp[1][0][0];
	int cc=0;for(int i=1;i<=(n<<1);i+=2) cc+=((!~a[i])&&(!~a[i+1]));
	for(int i=1;i<=cc;i++) res=1ll*res*i%MOD;
	printf("%d\n",res);
	return 0;
}
/*
6
-1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
*/