P12992

简化题意

\(n\) 个点的无向图,初始没有边。重复以下操作:

从所有 \(\deg_u=0\lor\deg_v=0\) 的点对 \((u,v)\) 中等概率选一个,加边 \((u,v)\)

无法进行操作时停止。给定 \(m\),求恰好 \(m\) 次操作后停止的概率,答案对给定质数 \(P\) 取模。这里的 \(n,m\) 对应原题的 \(M,M-K\)

\(n\le 5\)

暴搜即可。

点击查看代码
//by lovely Autumn_Rain
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define For(i,l,r) for(int i=l;i<=r;i++)
int n,m,P;
vector<int>deg;
ll ans=0;
ll qpow(ll a,ll b){
    ll res=1;
    while(b){
        if(b&1)res=res*a%P;
        a=a*a%P;b>>=1;
    }
    return res;
}
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
void dfs(int cnt,vector<int>&deg,ll res){
    bool f=0;
    For(i,1,n)if(deg[i]==0){f=1;break;}
    if(!f){
        if(cnt==m)ans=(ans+res)%P;
        return;
    }
    vector<pii>G;
    ll num=0;
    For(u,1,n-1){
        For(v,u+1,n){
            if(!deg[u]||!deg[v]){
                G.pb(mp(u,v));
                num++;
            }
        }
	}
    if(num==0)return;
    ll inv=qpow(num,P-2)%P;
    for(pii &x:G){
        int u=x.fi,v=x.se;
        deg[u]++;deg[v]++;
        dfs(cnt+1,deg,res*inv%P);
        deg[u]--;deg[v]--;
    }
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin>>n>>m>>P;
    deg.resize(n+1,0);
    dfs(0,deg,1);
    cout<<ans%P;
    return 0;
}

\(n\le 4\times 10^3\)

考虑 \(O(n^2)\) 动态规划。\(f_{i,j}\) 表示操作 \(i\) 次,现在还有 \(j\) 个孤立点的概率。

\[f_{i+1,j-2}=\binom{j}{2}f_{i,j} \]

\[f_{i+1,j-1}=j\times(n-j)\times f_{i,j} \]

点击查看代码
//by lovely Autumn_Rain
#include<bits/stdc++.h> 
using namespace std; 
#define ll long long 
#define For(i,l,r) for(int i=l;i<=r;i++) 
int n,m;
ll P;
const int N=4e3+10;
ll f[N][N];
ll qpow(ll a,ll b){
	ll res=1;
	while(b){
		if(b&1)res=res*a%P;
		a=a*a%P;
		b>>=1;
	}
	return res;
}
int main(){ 
	ios::sync_with_stdio(0); 
	cin.tie(0);cout.tie(0); 
	cin>>n>>m>>P;
	f[0][n]=1;
	For(i,0,m-1){
		for(int j=n;j>=1;j--){
			if(f[i][j]==0)continue;
			ll t=(n*(n-1)/2-(n-j)*(n-j-1)/2+P)%P;
			ll inv=qpow(t,P-2)%P;
			if(j>=2)f[i+1][j-2]=(f[i+1][j-2]+j*(j-1)/2*inv%P*f[i][j]%P+P)%P;
			if(j>=1)f[i+1][j-1]=(f[i+1][j-1]+j*(n-j)*inv%P*f[i][j]%P+P)%P;
		}
	}
	cout<<(f[m][0]+P)%P;
	return 0; 
}

\(m=n-1\)

打表或者推式子,答案为 \(\frac{2^{n-2}}{Catalan_{n-1}}\)

点击查看代码
//by lovely Autumn_Rain
#include<bits/stdc++.h> 
using namespace std; 
#define ll long long 
#define For(i,l,r) for(int i=l;i<=r;i++) 
int n,m;
ll P;
const int N=4e3+10;
ll f[N][N];
ll qpow(ll a,ll b){
	ll res=1;
	while(b){
		if(b&1)res=res*a%P;
		a=a*a%P;
		b>>=1;
	}
	return res;
}
ll cal(int x){
	ll inv=qpow(x+1,P-2)%P;
	ll res=1;
	For(i,x+1,2*x){
		res=res*i%P;
		int ar=i-x;
		ll t=qpow(ar,P-2)%P;
		res=res*t%P;
	}
	return res*inv%P;
}
void solve(){
	ll res=cal(n-1);
	ll inv=qpow(res,P-2)%P;
	cout<<qpow(2,n-2)*inv%P;
}
int main(){ 
	ios::sync_with_stdio(0); 
	cin.tie(0);cout.tie(0); 
	cin>>n>>m>>P;
	if(m==n-1){solve();return 0;}
	f[0][n]=1;
	For(i,0,m-1){
		for(int j=n;j>=1;j--){
			if(f[i][j]==0)continue;
			ll t=(n*(n-1)/2-(n-j)*(n-j-1)/2+P)%P;
			ll inv=qpow(t,P-2)%P;
			if(j>=2)f[i+1][j-2]=(f[i+1][j-2]+j*(j-1)/2*inv%P*f[i][j]%P+P)%P;
			if(j>=1)f[i+1][j-1]=(f[i+1][j-1]+j*(n-j)*inv%P*f[i][j]%P+P)%P;
		}
	}
	cout<<(f[m][0]+P)%P;
	return 0; 
}

\(O(n)\) 正解

两种操作情况。

  • 一个孤立点和一个非孤立点操作。
  • 两个孤立点连边。

假设做了 \(c_0\) 次操作一,\(c_1\) 次操作二,那么有:

\(\because c_0+c_1=m\)

\(\ \ \ \ c_0+2\times c_1=n\)

\(\therefore c_1=n-m\)

不妨假设 \(k=n-m\),那么 \(k\) 就是操作二的数目。

总共有 \(\binom{n}{2}\) 条边,这些边任意顺序操作共 \(\binom{n}{2}!\) 种情况。我们考虑在所有情况里筛选符合题意的排列。每次操作到不合法的情况就操作跳过(可以发现这并不影响概率)。

此时可以进行操作二,要求两个点先前没有连过边,也就是不存在先前就已经操作完的相邻边。

我们希望刚好选出 \(k\) 条这样的操作二边。

这个问题太难了,考虑至少选出 \(k\) 条操作二边怎么做,也就是选 \(x\geq k\) 条出来。如果解决了这个问题,就可以容斥一下 \(k\) 的贡献,最终得出刚好 \(=k\) 条操作二边的答案。具体的容斥系数是 \((-1)^{x-k}\binom{x}{k}\)

此时的排列方案数相当于一个 DAG 的拓扑序个数,要求这 \(x\geq k\) 条边比相邻的边要更早选择。我们人为钦定一下这 \(x\) 条边的顺序(最后再乘上排列数 \(\binom{n}{2x}(2x-1)!!\) 就可以了)。

现在相当于,有 \(\binom{n}{2}\) 个点,已经连下来一条链 \((0,1),(2,3)\cdots(2x-2,2x-1)\)。对于剩下的边,要求它们通过某种链接方法,保证自己不会成为符合操作二要求的边,且满足可以不破坏钦定的 \(x\) 条操作二的边。

因此,如果该边和至少某个特殊边相邻,那么它在树上的父亲就是它相邻的特殊边中编号最靠前的一个。如果它不和特殊边相邻,顺序不重要,可以直接连到 \((2x-2,2x-1)\)。这样连出来会是一颗树。答案就是这颗树的拓扑序个数。

为什么是一棵树?

选出来的 \(x\) 条边钦定顺序编号后:\((0,1),(2,3)\cdots(2x-2,2x-1)\)

对于边 \((i,j)\),有如下情况:

  • \(i>2x-1 \ \land \ j>2x-1\)

    此时边 \((i,j)\) 没有限制。

  • \(\exists \ i \lor j\) 是钦定边的某端点。

    选编号最靠前的那个端点作为另一个点的父亲。

此时发现,每个点父亲是唯一的。那么这个 DAG 就是一棵树。

\(n\) 个点的树的拓扑序个数:

\[n!\times \prod_{i=1}^{n}\frac{1}{sz_i} \]

我们发现按照上面那种连接方法是可以算出每个子树的 \(sz\) 的。非钦定边都是叶子结点,可以不用考虑。与 \((2x-2,2x-1)\) 相邻的边有 \(2n-4\) 条。与 \((2x-4,2x-3)\) 相邻且不与 \((2x-2,2x-1)\) 相邻的边有 \(2n-6\) 条……

因此钦定点的 \(sz_i=1+\sum_{j\le i}(2n-4j)\)

拓扑序个数:

\[\binom{n}{2}!\times \prod_{i=1}^{x}\frac{1}{i+\sum_{j=1}^{i}(2n-4j)} \]

\[\binom{n}{2}!\times\prod_{i=1}^{x}\frac{1}{(-2i^2+2ni-i)} \]

\[\binom{n}{2}!\times\frac{1}{x!}\prod_{i=1}^{x}\frac{1}{(-2i+2n-1)} \]

\[\binom{n}{2}!\times\frac{(2n-3-2x)!!}{x!(2n-3)!!} \]

因为求的是概率,所以去掉了全情况 \(\binom{n}{2}!\),答案是:

\[\frac{(2n-3-2x)!!}{x!(2n-3)!!} \]

因为一开始钦定 \(x\) 条边一个顺序。总共有 \(x!\) 种顺序,每种顺序的答案都是这个,所以最终答案里分母的 \(x!\) 抵掉了。

算上容斥和顺序数,最终结果:

\[\sum_{x=k}^{\left \lfloor \frac{n}{2} \right \rfloor }(-1)^{x-k}\binom{x}{k}\binom{n}{2x}(2x-1)!!\frac{(2n-3-2x)!!}{(2n-3)!!} \]

时间复杂度 \(O(n)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define For(i,l,r) for(int i=l;i<=r;i++)
int n,m,k,p;
const int N=5e6+10;
ll fac[N],ifc[N];
ll f[N],g[N],s[N];
ll qpow(ll a,ll b){
	ll res=1;
	while(b){
		if(b&1)res=res*a%p;
		a=a*a%p;
		b>>=1;
	}
	return res;
}
ll C(int n,int m){
	if(n<m||m<0)return 0;
	return (fac[n]*ifc[m]%p*ifc[n-m]%p)%p;
}
ll ans;
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>k>>p;k=n-k;
	if(k*2>n){cout<<0<<'\n';return 0;}
	m=n>>1;
	For(i,1,m){
		s[i]=s[i-1]+2*(n-2*i)+1;
		s[i]%=p;
	}
	fac[0]=f[0]=1;
	For(i,1,m){
		fac[i]=i*fac[i-1]%p;
		f[i]=s[i]*f[i-1]%p;
	}
	ifc[m]=qpow(fac[m],p-2);
	g[m]=qpow(f[m],p-2);
	for(int i=m;i>=1;i--){
		ifc[i-1]=i*ifc[i]%p;
		g[i-1]=s[i]*g[i]%p;
	}
	For(i,1,m){
		ll t=1ll*(n-2*i+1)*(n-2*i+2)/2;
		f[i]=(t%p*f[i-1])%p;
	}
	For(i,k,m){
		if((i-k)&1)ans=(ans+(2*p-C(i,k))*(g[i]%p*f[i]%p)%p)%p;
		else ans=(ans+C(i,k)*(g[i]%p*f[i]%p)%p)%p;
	}
    cout<<ans%p;
	return 0;
}
posted @ 2025-11-12 21:43  Accept_Reality  阅读(11)  评论(0)    收藏  举报