Loading

题解 AT4515 [AGC030F] Permutation and Minimum

题意

有一个 \(2 N\) 个数的序列 \(A\),从 \(1\)\(2 N\) 标号。你要把 \(1 \sim 2 N\) 这些数填进去,使它形成一个排列。

但是已经有一些位置强制填了特定的数了,输入时会给出。

最后令长度为 \(N\) 的序列 \(B\) 为:令 \(B_i = \min\{A_{2 i - 1}, A_{2 i}\}\)

询问所有方案中能得到的不同的 \(B\) 的数量。

\(1 \le N \le 300\)

思路

我们把\((A_{2i},A_{2i-1})\)称作第\(i\)对数。显然,一对数只有至少一个数为-1,才会对答案有贡献

接着就是这些有答案贡献的对填空,将没有填的数字从大到小排序来考虑

为什么是从大到小填?假设从小到大填,假设填了一个数对\((i,-1)\),你会发现这个数对无论填\(i+1\)还是\(i+2\),实际上方案数并没有增加,而我们会重复计算,具有后效性。但从大到小并没有这种担忧,可以始终保证答案不重不漏

\(f(i,j,k)\)表示当前考虑到第\(i\)个数,还剩\(j\)个只填了1个数的对(不包含被限定一位的对),称作\((-1,a)\),\(k\)个原本就限定了一位的对,称作\((-1,x)\)

考虑前\(i-1\)个数已经讨论,现在考虑第\(i\)个数,进行分类讨论

如果它是\((-1,x)\)中的\(x\)(也就是被限定的数字),那么它可以不匹配,\(f(i-1,j,k)\)转移到\(f(i,j,k+1)\),也可以匹配与一个填了一个数的\((-1,a)\)匹配,那么\(f(i,j,k)\)转移到\(f(i,j-1,k)\)

如果不是,那么它可以不匹配,\(f(i-1,j-1,k)\)转移到\(f(i,j,k)\);也可以填入\((-1,a)\)\(f(i-1,j+1,k)\)转移到\(f(i,j,k)\);还可以填入\((-1,x)\),因为任意\(x\)的位置固定,所以会有\(k+1\)种填入方案,\((k+1)\times f(i-1,j,k+1)\)转移到\(f(i,j,k)\)

然后我们就讨论完了,初状态\(f(0,0,0)=1\)

又因为原本都是-1的数对是无序的,所以最终答案还得乘上原本都是-1的数对个数的阶乘

感悟

从这一题中我们可以发现,设计状态转移方程的时候,不一定要以“如何计算出某一个状态入手”,也可以换一个思路,思考“这一个状态可以更新哪些后面的状态”,根据具体情况来选择更加简单,顺畅的思路

代码

#include<bits/stdc++.h>
using namespace std;
int const MAXN=610,MOD=1e9+7;
int n,cnt1,cnt2,m,ans=1;
int a[MAXN],vis[MAXN],f[MAXN][MAXN][MAXN];
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
int main(){
	scanf("%d",&n);n*=2;
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<=n;i+=2){
		if(a[i]>-1 &&a[i+1]>-1){
			vis[a[i]]=vis[a[i+1]]=2;
		}else if(a[i]<0 && a[i+1]<0){
			cnt1++;
		}else{
			cnt2++;
			if(a[i]>-1)vis[a[i]]=1;
			if(a[i+1]>-1)vis[a[i+1]]=1;
		}
	}
	for(int i=n;i>=1;i--){
		if(vis[i]<2)a[++m]=i;
	}
	f[0][0][0]=1;
	for(int i=1;i<=m;i++){
		for(int j=0;j<=n;j++){
			for(int k=0;k<=n;k++){
				if(vis[a[i]]==1){
					if(k)add(f[i][j][k],f[i-1][j][k-1]);
					add(f[i][j][k],f[i-1][j+1][k]);
				}else{
					if(j)add(f[i][j][k],f[i-1][j-1][k]);
					add(f[i][j][k],f[i-1][j+1][k]);
					add(f[i][j][k],1ll*f[i-1][j][k+1]*(k+1)%MOD);
				}
			}
		}
	}
	ans=f[m][0][0];
	for(int i=1;i<=cnt1;i++)ans=1ll*ans*i%MOD;
	printf("%d\n",ans%MOD);
	return 0;
}

参考资料

yyb的博客

万神的博客

posted @ 2020-11-11 14:45  fpjo  阅读(163)  评论(1编辑  收藏  举报