CF1466H - Finding satisfactory solutions

给出一个排列\(A\),询问合法的数组\(p_i\)的个数,数组中每个元素\(p_i\)为一个排列。如果合法,当且仅当:不能找到一个排列\(B_i\),使得存在一个集合\(S\),满足:

  1. \(\forall i\in S,B_i\in S\)
  2. \(\forall i\in S\),在排列\(p_i\)中,不存在\(A_i\)\(B_i\)左边出现。
  3. \(\exist i\in S\),在排列\(p_i\)中,\(A_i\)\(B_i\)右边出现。

\(n\le 40\)


由于题面太长直接去看了洛谷的翻译,后来感觉不对劲才知道洛谷的翻译已经把算法的第一步说出来了……

可以把限制建成一个图:对于某个\(i\),如果\(j\)\(A_i\)前出现,则连黑边\((i,j)\);连白边\((i,A_i)\)

发现如果满足题目的限制,当且仅当:这个图中的所有环中不存在黑边。

理解:

由于\(\forall i\in S,B_i\in S\),可以视作\(S\)为若干个环的集合。可以只考虑一个环。此时\((i,B_i)\)视作\(i\)的出边。

此时如果存在\(A_i\neq B_i\),即触犯了限制3,也就是环中存在黑边。

先连边\((i,A_i)\),由于\(A_i\)是排列所以形成的是若干个环。现在要往图中加入若干条边,使其不出现新的环。此时贡献为\(\prod out_i!(n-out_i-1)!\)

把环缩点,问题类似于DAG计数。然后状压DP。大小一样的环等价可以压在一起。设\(f_S\)表示\(S\)中的点组成的DAG的贡献。这里\(S\)用高维向量表示。状态不会很多,设\(p(n)\)表示\(n\)划分成的可重集合中,不同的可重集合的个数最多是多少。\(p(40)=1440\)

\[f_S=\sum_{T\subseteq S} f(S\setminus T)\binom{S}{T}(-1)^{|T|+1}calc(sum(T),sum(S\setminus T))\\ calc(p,q)=(\sum_{i} \binom{q}{i} i!(n-1-i)!)^p \]

\(\binom{S}{T}\)就是各个维度的组合数乘积。

直接这样算大概是\(O(p^2(n)n)\)的。

这个东西可以辅助转移。考虑到\(T\)转移到\(T+\Delta\),可以给\(\Delta\)一位位分别计算。另外设个\(DP\)存下\(T+\Delta\),当前处理到第几位,以及\(sum_T\)是多少(用于计算\(calc\)产生的贡献)。设\(g_{S,i,j}\),具体见程序。时间\(O(p(n)n^3)\)


using namespace std;
#include <bits/stdc++.h>
#define N 45
#define ll long long
#define mo 1000000007
ll qpow(ll x,ll y=mo-2){
	ll r=1;
	for (;y;y>>=1,x=x*x%mo)
		if (y&1)
			r=r*x%mo;
	return r;
}
int n;
int a[N];
int c[N],p[N];
ll fac[N*2],ifac[N*2];
void initC(int n){
	fac[0]=1;
	for (int i=1;i<=n;++i)
		fac[i]=fac[i-1]*i%mo;
	ifac[n]=qpow(fac[n]);
	for (int i=n-1;i>=0;--i)
		ifac[i]=ifac[i+1]*(i+1)%mo;
}
ll C(int m,int n){return fac[m]*ifac[n]%mo*ifac[m-n]%mo;}
void init(){
	scanf("%d",&n);
	initC(n*2);
	for (int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	static int bz[N];
	for (int i=1;i<=n;++i)
		if (!bz[i]){
			int t=0;
			for (int x=i;!bz[x];x=a[x])
				bz[x]=1,t++;
			c[t]++;
		}
	p[0]=1;
	for (int i=1;i<=n;++i)
		p[i]=p[i-1]*(c[i]+1);
}
ll q[N];
ll f[1440];
ll g[1440][N][N];
void doit(){
	for (int i=0;i<=n;++i){
		ll s=0;
		for (int j=0;j<=i;++j)
			(s+=C(i,j)*fac[j]%mo*fac[n-1-j])%=mo;
		q[i]=s;
	}
	for (int i=0;i<p[n];++i){
		for (int t=0;t<=n;++t)
			for (int j=1;j<=n;++j){
				int w=i%p[j]/p[j-1];
				ll qj=qpow(q[t],j),tmp=1;
				for (int k=w;k<=c[j];++k){
					(g[i+(k-w)*p[j-1]][j][t]+=g[i][j-1][t]*ifac[k-w]%mo*tmp)%=mo;
					tmp=tmp*(-1)*qj%mo;
				}
			}
		ll S=1,s=0;
		if (i==0)
			f[0]=1;
		else{
			for (int j=1;j<=n;++j){
				int w=i%p[j]/p[j-1];
				S=S*fac[w]%mo;
				s+=w*j;
			}
			ll tmp=0;
			for (int j=0;j<=s;++j)
				(tmp+=g[i][n][j])%=mo;
			f[i]=tmp*S%mo;
		}
//		(g[i][0][s]+=f[i]*qpow(S)*(-1))%=mo;
		ll v=f[i]*qpow(S)*(-1)%mo;
		for (int j=1;j<=n;++j){
			int w=i%p[j]/p[j-1];
			ll qj=qpow(q[s],j),tmp=(-1)*qj;
			for (int k=w+1;k<=c[j];++k){
				(g[i+(k-w)*p[j-1]][j][s]+=v*ifac[k-w]%mo*tmp)%=mo;
				tmp=tmp*(-1)*qj%mo;
			}
		}
	}
}
int main(){
	freopen("in.txt","r",stdin);
	init();
	doit();
//	for (int i=0;i<p[n];++i)
//		printf("%lld\n",f[i]);
	ll ans=f[p[n]-1];
	ans=(ans+mo)%mo;
	printf("%lld\n",ans);
	return 0;
}
posted @ 2021-02-24 18:38  jz_597  阅读(199)  评论(2编辑  收藏  举报