AGC030F

11 篇文章 0 订阅
4 篇文章 0 订阅

luogu链接

解法

考虑如果A中的两个相邻位置都确定了的话,这两个位置所对应的B的位置不会对B的方案产生影响,直接不管就可以了。
如果建立图论模型,可以发现B的每一个数相当于对A中的两个数匹配后,较小的值
然后记录每个数是否在A中出现过,然后可以从大到小dp:
f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示考虑了 ≥ i \ge i i的所有位置,其中有 j j j个已经在A中出现的还需要向比i更小的位置匹配,有 k k k个没有在A中出现的需要向比i更小的位置匹配。
转移考虑从 i + 1 i+1 i+1转移过来:
讨 论 i 是 否 在 A 中 出 现 过 : 讨论i是否在A中出现过: iA
如 果 出 现 过 : f [ i ] [ j ] [ k ] + = f [ i + 1 ] [ j − 1 ] [ k ] 如果出现过:f[i][j][k]+=f[i+1][j-1][k] f[i][j][k]+=f[i+1][j1][k],这里的意思是i也要和更小的数匹配
如 果 出 现 过 : f [ i ] [ j ] [ k ] + = f [ i + 1 ] [ j ] [ k + 1 ] 如果出现过:f[i][j][k]+=f[i+1][j][k+1] f[i][j][k]+=f[i+1][j][k+1],这里是i和没有出现在A中的更大的数匹配。注意i不能和已经出现在A中的更大的数匹配,因为如果要匹配的话,这两个数必须相邻,这种情况一开始已经考虑过了$
如果没出现过,则可以和更小的数匹配,或者和任意更大的数匹配,但是如果匹配的是已经出现过的更大的数,因为匹配任意一个都可以,所以要乘上个数,如果是未出现的,因为所有未出现的可以互相替换顺序,所以一开始算上一个所有未出现位置个数的阶乘

#include<bits/stdc++.h>
using namespace std;
const int maxn=605;
const int mod=1e9+7;
inline int read(){
	char c=getchar();int t=0,f=1;
	while((!isdigit(c))&&(c!=EOF)){if(c=='-')f=-1;c=getchar();}
	while((isdigit(c))&&(c!=EOF)){t=(t<<3)+(t<<1)+(c^48);c=getchar();}
	return t*f;
}
int n,a[maxn],b[maxn],t,c[maxn];
int f[2][maxn][maxn];
inline void add(int &a,int b){
	a=a+b;
	if(a>=mod)a=a-mod;
	if(a<0)a=a+mod;
}
signed main(){
	n=read();
	for(int i=1;i<=2*n;i++)a[i]=read();
	int cnt=0;
	for(int i=1;i<=2*n;i+=2){
		if((a[i]!=-1)&&(a[i+1]!=-1))b[a[i]]=b[a[i+1]]=2;
		else if(a[i]!=-1){b[a[i]]=1;}
		else if(a[i+1]!=-1){b[a[i+1]]=1;}
		else cnt++;
	}
	for(int i=1;i<=2*n;i++){
		if(b[i]==1)c[++t]=1;
		else if(b[i]==0)c[++t]=2;
	}
	f[t&1][0][0]=1;
	for(int i=t;i>=1;i--){
		memset(f[(i-1)&1],0,sizeof(f[(i-1)&1]));
		for(int j=0;j<=t&&j<=n;j++){
			for(int k=0;k<=t&&k<=n;k++){
				if(c[i]==1){
					add(f[(i-1)&1][j+1][k],f[i&1][j][k]);
					if(k)add(f[(i-1)&1][j][k-1],f[i&1][j][k]);
				}
				else{
					add(f[(i-1)&1][j][k+1],f[i&1][j][k]);
					if(k)add(f[(i-1)&1][j][k-1],f[i&1][j][k]);
					if(j)add(f[(i-1)&1][j-1][k],(1ll*f[i&1][j][k]*j)%mod);
				}
			}
		}
	}
	int ans=f[0][0][0];
	for(int i=1;i<=cnt;i++)
	ans=1ll*ans*i%mod;
	printf("%d\n",ans);
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值