CF1466H - Finding satisfactory solutions

CF1466H - Finding satisfactory solutions

题目大意

给定一组置换\(a_i\),现在对于每个元素\(i\)构造一个排列\(p_i\)

有两类边

1.\(i\rightarrow a_i\)

2.表示\(i\)\(p_i\)中所有在\(i\)前面出现的数\(p_{i,j}\)连有向边

现在要求最终的图里没有2类边出现在任何一个环上

\(n\leq 40\)

图分析

置换显然构成一堆环,答案只与每个置换环的大小有关

把置换环缩点,2类边就构成置换环之间的拓扑图

计算答案

由于连边方式比较奇怪,\(i\)向任何\(j\)个点连边的方案数是\(j!(n-1-j)!\)

考虑对于缩点之后的点进行拓扑图\(dp\),这是一个经典问题,参考 [CEOI2019] 游乐园 | [CEOI2019] Amusement Park

因此需要一个枚举子集的过程

而由于这题的特殊性,每个点实际上只与其对应置换环答案有关

把一类点放在一起计算,枚举时额外乘上一个组合数即可

实际实现时并不需要把所有点分类,可以只把大小为1,2的点拿出来,剩下的直接状压


const int N=42,P=1e9+7;

int n,m;
int p[N],vis[N],C[N][N];
int a[N],W[N][N],J[N],sz[1<<14];
vector<vector<vector<int>>> dp; // 爽!!!

int main() {
	n=rd();
	rep(i,1,n) p[i]=rd();
	rep(i,0,n) rep(j,*C[i]=1,i) C[i][j]=(C[i-1][j-1]+C[i-1][j])%P;
	rep(i,*J=1,n) J[i]=1ll*J[i-1]*i%P;
	int c1=0,c2=0;
	rep(i,1,n) if(!vis[i]) {
		int c=0;
		for(int j=i;!vis[j];j=p[j]) vis[j]=1,c++;
		if(c==1) c1++;
		else if(c==2) c2++;
		else a[m++]=c;
	}
	rep(i,0,n-1) {
		int t=0;
		rep(j,0,i) t=(t+1ll*C[i][j]*J[j]%P*J[n-j-1])%P;
		rep(j,*W[i]=1,n) W[i][j]=1ll*W[i][j-1]*t%P;
	}
	int A=(1<<m)-1;
	rep(i,1,A) sz[i]=sz[i&(i-1)]+a[__lg(i&-i)];
	dp.resize(c1+1);
	rep(i,0,c1) {
		dp[i].resize(c2+1);
		rep(j,0,c2) dp[i][j].resize(A+1);
	}
	dp[0][0][0]=1;
	rep(i,0,c1) rep(j,0,c2) rep(S,0,A) {
		int c=sz[S]+i+2*j;
		rep(a,0,c1-i) rep(b,0,c2-j) {
			int R=A^S;
			for(int T=R;;T=(T-1)&R) {
				if(!a && !b && !T) break;
				int d=sz[T]+a+b*2,parity=(__builtin_parity(T)^a^b)&1;
				dp[a+i][b+j][S|T]=(dp[a+i][b+j][S|T]+1ll*(parity&1?1:-1)*W[c][d]*dp[i][j][S]%P*C[a+i][i]%P*C[b+j][j])%P;
				if(T==0) break;
			}
		}
	}
	int ans=(dp[c1][c2][A]+P)%P;
	printf("%d\n",ans);
}




posted @ 2021-05-29 14:03  chasedeath  阅读(134)  评论(0编辑  收藏  举报