[AGC030F] Permutation and Minimum

又被计数题创思力。

Description

洛谷传送门 & AT 传送门

Solution

考虑建立图论模型:建立一张包含 2n 个点的图,将所有 (A2i1,A2i) 连边。

首先,考虑一种连边会对应多少种不同的 B。为方便叙述,我们称 x[1,2n] 被确定,当且仅当 xA 中的位置已确定。

显然,若 A 中只有 1,那么可以将所有边的左端点任意排列,有 n! 种方案,对应 n! 种不同的 B。对于一般情况,我们先删去所有两端均确定的边(对于所有满足 (A2i1,A2i) 均确定的 i,将 A2i1,A2i 两点一同删去),然后将剩余的点重编号,并将边分为两类:

  • 两端 u,v 一者被确定(称其为一类边)。此时,min(u,v)B 中的位置已被确定。
  • 两端 u,v 均未被确定(称其为二类边)。此时,这些 min(u,v) 可以在 B 中任意排列,产生 sz! 的贡献,其中 sz 表示这样的 (u,v) 的数量。

注意到 sz 即为满足 A2i11,A2i1i(i[1,n]) 的数量,与具体连边无关。最后计算答案时乘上即可。

但答案显然不是连边方案数 ×sz!,因为可能存在本质相同的连边方案。具体来说,两种连边方案本质不同,当且仅当:

  • 所有二类边的左端点组成的可重集不同。
  • 存在被确定的 x,使得包含 x 的一类边的左端点不同。

现在,我们的任务是:计算本质不同的连边方案数。

考虑从后往前 dp,令 fi,x,y 表示,看了编号 i 的点,其中有 x 个被确定的点尚未匹配,有 y 个未被确定的点尚未匹配。转移考虑如下五种情况:

  • i1 未被确定
    • 匹配一个未被确定的点,相当于在可重集中加入 i1,转移到 fi1,x1,y
    • 匹配一个被确定的点,根据之前 本质不同 的定义,我们关心其另一端的确定点,因此以 y 的转移系数转移到 fi1,x,y1
    • 暂时不匹配 i1,转移到 fi1,x,y+1
  • i1 被确定。
    • 匹配一个未被确定的点,此时以 i1 为一端的一类边的左端点必然是 i1,我们并不关心其另一端的不确定点,因此直接转移到 fi1,x,y1
    • 匹配一个被确定的点,这是不可能的。
    • 暂时不匹配 i1,转移到 fi1,x+1,y

边界:fn+1,0,0=1
答案:f1,0,0

转移是 O(1) 的,时间复杂度 O(n3),可以通过本题。

Code

#include <bits/stdc++.h>
#define int long long
#define inf 8200000000000000000
using namespace std;
const int N=605,mod=1e9+7;

int read(){
	int s=0,w=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-')  w=-w;ch=getchar();}
	while (ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+(ch^'0');ch=getchar();}
	return s*w;
}
int n,m,sz,ckp[N],f[N][N],g[N][N],vis[N];
void chkadd(int x,int &y){y+=x;if(y>inf)y%=mod;}
void Swap(int &x,int &y){
	if (y)  y%=mod;
	x=y,y=0;
}
signed main(){
	n=read();
	for (int i=1;i<=n;i++){
		int x=read(),y=read();
		if (x<0&&y<0)  sz++;
		else if (x>0&&y>0)  vis[x]=vis[y]=2;
		else if (x>0)  vis[x]=1;
		else vis[y]=1;
	}
	for (int i=1;i<=(n<<1);i++){
		if (vis[i]<2)  ckp[++m]=i;
	}
	sort(ckp+1,ckp+m+1,greater<int>()),f[0][0]=1;
	for (int t=1;t<=m;t++){
		bool typ=vis[ckp[t]];
		for (int x=0;x<=min(n,t-1);x++){
			for (int y=0,R=min(t-1-x,n);y<=R;y++){
				int val=f[x][y];
				if (!val)  continue;
				if (!typ){
					if (x)  chkadd(val*x,g[x-1][y]);
					if (y)  chkadd(val,g[x][y-1]);
					chkadd(val,g[x][y+1]);
				}
				else{
					if (y)  chkadd(val,g[x][y-1]);
					chkadd(val,g[x+1][y]);
				}
			}
		}
		for (int x=0;x<=min(n,t);x++){
			for (int y=0,R=min(t-x,n);y<=R;y++)  Swap(f[x][y],g[x][y]);
		}
	}
	int ans=f[0][0];
	for (int i=1;i<=sz;i++)  ans=(ans*i)%mod;
	return cout<<ans,0;
}
posted @   ducati❤OI  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示