在 @aSunnyDay 大佬的帮助下终于调出来了,感觉此题推导公式的过程比较有趣,遂作文以记之。
平凡情形:
m>n 时答案为
0,
m=n 时为
2(n−1)!。
当
m<n 时,观察到
n 点
m 边的“半环”图为
n−m 条不交的链之并(链可以为孤立点)。
我们考虑将链重组为一条长度为
n 的链,对重组的方法数进行算两次。
一方面,对于每条
n 个顶点的链,有多少种方法将其拆分为
n−m 条链?
不妨设链为
1−2−...−n。 设拆分的
n−m 条链分别为
[1,r1],[r1+1,r2],...,[rn−m−1+1,n]。有
1≤r1<r2<...<rn−m−1≤n−1。故方法数为
(n−m−1n−1)=(mn−1)。
另一方面,对于
n−m 条(短)链,有多少种方法拼装为一条(长)链?
n−m 条链顺序可任意排列,有
(n−m)! 种;每条短链可进行翻转,有
2n−m 种;每条长链被计算两次(其和翻转各被计算两次)。
故拼装方法数为
2(n−m)!⋅2n−m=(n−m)!⋅2n−m−1??
错误之处在于若某条短链为孤立点,将其翻转后所得长链相同,计算出现重复。
解决方法:枚举孤立点个数,对其余点构成的短链(长度至少为
1)重组为长链并进行算两次。
设孤立点个数为
n−k,选出方法数为
(kn)。
算两次计算其余
k 点划分为
k−m 条链,每条链长度均至少为
1 的方法数。
一方面,对于每条
k 个顶点的链,有多少种方法将其拆分为
k−m 条长度(边数)均至少为
1 的链?类似地,不妨设链为
1−2−...−k。设拆分的
k−m 条链分别为
[1,r1],[r1+1,r2],...,[rk−m−1+1,k]。有
1<r1<r2<...<rk−m−1<n−1,且
∀i=1,2,...,k−m−2,ri+1−ri>1。
作变换
ri→ri’=ri−i。有
1≤r1’<r2’<...<rk−m−1’≤m−1。故方法数为
(k−m−1m−1)=(2m−km−1) 为定值。
另一方面,对于
k−m 条长度至少为
1 的短链,有多少种方法将其拼装为一条
k 点长链?类似地,
k−m 条链顺序可任意排列,有
(k−m)! 种;每条短链可进行翻转,有
2k−m 种;每条长链被计算恰两次(其和翻转各被计算两次)。故拼装方法数为
2(k−m)!⋅2k−m=(k−m)!⋅2k−m−1。
由算两次原理知
k 个点的由
k−m 条长度均至少为
1 的不交链构成的图的个数为
(k−m)!⋅2k−m−121k!⋅(k−m−1m−1)=(k−m−1)!⋅(k−m)!⋅(2m−k)!⋅2k−m−1(m−1)!⋅k!。
易知
k 的取值范围为
m+1≤k≤min(2m,n)。
故最终答案为
∑k=m+1min(2m,n)(k−m−1)!⋅(k−m)!⋅(2m−k)!⋅2k−m−1(m−1)!⋅k!⋅(kn)=∑k=m+1min(2m,n)(k−m−1)!⋅(k−m)!⋅(2m−k)!⋅(n−k)!⋅2k−m−1(m−1)!⋅n!。
预处理
0,1,...,105 的阶乘及乘法逆元即可。
CPP#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1000000007;
ll t,m,n,inv[200009],f[200009],rf[200009],ans,p;
int main(){
f[0]=rf[0]=inv[1]=f[1]=rf[1]=1;
for(ll i=2;i<=200000;i++){
inv[i]=mod-mod/i*inv[mod%i]%mod;
f[i]=f[i-1]*i%mod;rf[i]=rf[i-1]*inv[i]%mod;
}
cin>>t;
while(t--){
cin>>n>>m;
if(m>n){cout<<0<<endl;continue;}
if(m==n){cout<<f[n-1]*(mod+1)/2%mod<<endl;continue;}
if(m==0){cout<<1<<endl;continue;}
ans=0;p=1;
for(ll k=m+1;k<=min(2*m,n);k++){
p*=(mod+1)/2;p%=mod;ans+=f[m-1]*f[n]%mod*rf[k-m-1]%mod*rf[k-m]%mod*rf[2*m-k]%mod*rf[n-k]%mod*p%mod;
ans%=mod;
}
cout<<ans<<endl;
}
return 0;
}