组合数学专项训练记录 2024.3

[abc221_e]LEQ

依题意得,当确定了两个端点后,中间的可选可不选,考虑枚举左端点,找比它大的右端点,求方案数,时间复杂度\(O(n^2)\),显然会T

考虑优化,若两个端点分别是i,j,则方案数为\(2^{j-i-1}=2^j\div 2^{i+1}\),所以考虑权值线段树记录\(2^j\),倒序枚举左端点即可

代码:

#include<cstdio>
#define ll long long
using namespace std;
const int p=1e9,mod=998244353;
int n,a[300005];
int ls[5000005],rs[5000005],idx,rt;
ll val[5000005];
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}
int add(int id,int l,int r,int x,int v)
{
	if(!id) id=++idx;
	if(l==r)
	{
		val[id]=(qpow(2,v)+val[id])%mod;
		return id;
	}
	int mid=(l+r)>>1;
	if(x<=mid) ls[id]=add(ls[id],l,mid,x,v);
	else rs[id]=add(rs[id],mid+1,r,x,v);
	val[id]=(val[ls[id]]+val[rs[id]])%mod;
	return id;
}
ll query_val(int id,int l,int r,int ql,int qr)
{
	if(!id||l>qr||r<ql) return 0;
	if(l>=ql&&r<=qr)
	{
		return val[id];
	}
	int mid=(l+r)>>1;
	ll res=0;
	if(mid>=ql) res=query_val(ls[id],l,mid,ql,qr);
	if(mid<qr) res=(res+query_val(rs[id],mid+1,r,ql,qr))%mod;
	return res%mod;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	ll ans=0;
	for(int i=n;i>0;i--)
	{
		ll cnt=query_val(rt,1,p,a[i],p);
	//	printf("%d ",cnt);
		ans=(ans+cnt*qpow(qpow(2,i+1),mod-2)%mod)%mod;
		rt=add(rt,1,p,a[i],i);
	}
	printf("%lld",ans%mod);
	return 0;
}

[abc243_f]Lottery

\(dp_{i,j,k}\)表示前i个东西,抽了j次,有k种的方案数,则转移方程为:

\[dp_{i,j,k}=\sum_{l=0}^jdp_{i-1,j-l,k-1} \]

代码:

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int p=998244353;
int n,m,K,sum,a[55];
ll dp[55][55][55],fac[55],inv[55];
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%p;
		x=x*x%p;
		y>>=1;
	}
	return res;
}
ll C(int n,int m)
{
	return fac[n]*inv[m]%p*inv[n-m]%p;
}
int main()
{
	scanf("%d%d%d",&n,&m,&K);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		sum+=a[i];
	}
	fac[0]=inv[0]=1;
	for(int i=1;i<=K;i++)
	{
		fac[i]=fac[i-1]*i%p;
	}
	inv[K]=qpow(fac[K],p-2);
	for(int i=K-1;i>0;i--)
	{
		inv[i]=inv[i+1]*(i+1)%p;
	}
	dp[0][0][0]=1;
	for(int i=1;i<=n;i++)
	{
		dp[i][0][0]=dp[i-1][0][0];
		for(int j=1;j<=K;j++)
		{
			for(int k=1;k<=min(i,m);k++)
			{
				dp[i][j][k]=dp[i-1][j][k];
				for(int l=1;l<=j;l++)
				{
					dp[i][j][k]=(dp[i][j][k]+dp[i-1][j-l][k-1]*C(j,l)%p*qpow(a[i],l)%p*qpow(qpow(sum,l),p-2)%p)%p;
				}
			}
		}
	}
	printf("%lld",dp[n][K][m]);
	return 0;
}

Claris的剑

可以发现,我们可以先构造一个序列:\(1,2,\dots,m\),再往其中插入相邻两数组成的二元组,分为两种情况:

  1. \(1,2,1,2,3,2,3,2,4,3,\dots,m-1,m\)

  2. \(1,2,1,2,3,2,3,2,4,3,\dots,m-1,m,m-1\)

二元组的个数为\(\lfloor\frac{n-m}{2}\rfloor\)

所以可以枚举n,m,答案为:\(1+\sum_{i=2}^n\sum_{j=2}^{m} C_{\lfloor\frac{i-j}{2}\rfloor+m-2}^{j-2}\),时间复杂度\(O(nm)\)

考虑优化,可以枚举\(\lfloor\frac{n-m}{2}\rfloor\),经化简,得\(ans=1+\sum_{i=0}^{\min(n-2,m-2)}C^{i+1}_{\lfloor\frac{n+i-2}{2}\rfloor+1}+\sum_{i=0}^{\min(n-2,m-2)}C^{i+1}_{\lfloor\frac{n+i-2}{2}\rfloor+1}\)

代码:

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int p=1e9+7;
ll fac[2000005],inv[2000005];
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%p;
		x=x*x%p;
		y>>=1;
	}
	return res;
}
ll C(int m,int n)
{
	return fac[n]*inv[m]%p*inv[n-m];
}
int n,m;
int main()
{
	scanf("%d%d",&n,&m);
	fac[0]=inv[0]=1;
	for(int i=1;i<=n;i++)
	{
		fac[i]=fac[i-1]*i%p;
	}
	inv[n]=qpow(fac[n],p-2);
	for(int i=n-1;i>0;i--)
	{
		inv[i]=inv[i+1]*(i+1)%p;
	}
	ll ans=1;
	for(int i=0;i<=min(n,m)-2;i++)
	{
		ans=(ans+C(i+1,(n+i-2)/2+1))%p;
	//	printf("%d %d %d\n",i+1,(n+i-2)/2+1,C(i+1,(n+i-2)/2+1));
	}
//	printf("%lld\n",ans);
	for(int i=0;i<=min(n-3,m-2);i++)
	{
		ans=(ans+C(i+1,(n+i-3)/2+1))%p;
	}
	printf("%lld",ans);
	return 0;
}

Array

很明显,当数组中的数确定下来后,顺序也一定确定了,所以考虑隔板发求每个元素取的个数,每个元素可取多个或不取,答案就是\(2\times C_{2n-1}^{n-1}\)

代码:

#include<cstdio>
#define ll long long
using namespace std;
const int p=1e9+7;
int n;
ll fac[2000005];
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%p;
		x=x*x%p;
		y>>=1;
	}
	return res;
}
int main()
{
	scanf("%d",&n);
	fac[0]=1;
	for(int i=1;i<=2*n;i++)
	{
		fac[i]=fac[i-1]*i%p;
	}
	ll ans=fac[2*n-1]*qpow(fac[n-1],p-2)%p*qpow(fac[n],p-2)%p;
	printf("%lld",(ans*2-n+p)%p);
	return 0;
}

Lucky Subsequence

可以发现,非幸运数字随便取,所以考虑如何求幸运数字的方案数

记第i种幸运数字的个数为\(cnt_i\),设\(dp_{i,j}\) 表示当前是第i种数字,1~i共取了j个的方案数,就是一个背包,式子:

\[dp_{i,j}=dp_{i-1,j}+dp_{i-1,j-1}\times cnt_i \]

记得滚动数组优化

代码:

#include<cstdio>
#include<algorithm>
#include<map>
#define ll long long
using namespace std;
const int p=1e9+7;
int n,k,a[100005],idx,sum,m;
ll dp[100005],cnt[100005],fac[100005],inv[100005];
map<int,int>mp;
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%p;
		x=x*x%p;
		y>>=1;
	}
	return res;
}
ll C(int n,int m)
{
	return fac[n]*inv[m]%p*inv[n-m]%p;
}
int main()
{
	scanf("%d%d",&n,&k);
	fac[0]=inv[0]=1;
	for(int i=1;i<=n;i++)
	{
		fac[i]=fac[i-1]*i%p;
	}
	inv[n]=qpow(fac[n],p-2);
	for(int i=n-1;i>0;i--)
	{
		inv[i]=inv[i+1]*(i+1)%p;
	}
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		int x=a[i],fl=0;
		while(x)
		{
			int r=x%10;
			if(r!=4&&r!=7)
			{
				fl=1;
				break;
			}
			x/=10;
		}
		if(!fl)
		{
			if(!mp[a[i]])
			{
				mp[a[i]]=++m;
			}
			cnt[mp[a[i]]]++;
		}
		else sum++;
	}
	dp[0]=1;
	for(int i=1;i<=m;i++)
	{
		for(int j=m;j>0;j--)
		{
			dp[j]=(dp[j]+dp[j-1]*cnt[i]%p)%p;
		}
	}
	ll ans=0;
	for(int i=0;i<=min(k,sum);i++)
	{
		ans=(ans+C(sum,i)*dp[k-i]%p)%p;
	//	printf("%d %d %d\n",i,C(n-idx,i),dp[k-i]);
	}
	printf("%lld",ans);
	return 0;
}

White, Black and White Again

很明显,当每一天发生哪种事,发生多少件定好后,方案数就是\(b!\times w!\)

所以可以枚举i,j,表示i~j天发生坏事,然后隔板法解决即可

代码:

#include<cstdio>
#define ll long long
using namespace std;
const int p=1e9+9;
int n,w,b;
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%p;
		x=x*x%p;
		y>>=1;
	}
	return res;
}
ll fac[4005],inv[4005],ans;
ll C(int n,int m)
{
	return fac[n]*inv[n-m]%p*inv[m]%p;
}
int main()
{
	scanf("%d%d%d",&n,&w,&b);
	fac[0]=1;
	for(int i=1;i<=4000;i++)
	{
		fac[i]=fac[i-1]*i%p;
	}
	inv[4000]=qpow(fac[4000],p-2);
	for(int i=3999;i>=0;i--)
	{
		inv[i]=inv[i+1]*(i+1)%p;
	}
	//printf("1");
	for(int i=2;i<n;i++)
	{
		for(int j=i;j<n;j++)
		{
			int l1=j-i+1;
			int l2=n-l1;
			if(l1>b||l2>w) continue;
			//printf("%d %d\n",l1,l2);
			ans=(ans+C(b-1,l1-1)*fac[b]%p*C(w-1,l2-1)%p*fac[w]%p)%p;
		//	printf("%d %d %d %d",C(l1-1,b-1),fac[b],C(l2-1,w-1),fac[w]);
		}
	}
	printf("%lld",ans);
	return 0;
}

Vasily the Bear and Beautiful Strings

可以发现,第一个1以后的1都没有用,因为连续的两个数中只要出现1必然会变成0

所以考虑枚举可取的第一个1的位置,用组合数求解即可

n=0,m=0,m=1要特判!!!

代码:

#include<cstdio>
#define ll long long
using namespace std;
const int p=1e9+7;
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%p;
		x=x*x%p;
		y>>=1;
	}
	return res;
}
ll fac[200005],inv[200005],ans;
ll C(int n,int m)
{
	return fac[n]*inv[m]%p*inv[n-m]%p;
}
int n,m,g;
int main()
{
	scanf("%d%d%d",&n,&m,&g);
	if(n==0)
	{
		if(m==1) printf("%d",g);
		else printf("%d",g^1);
		return 0;
	}
	if(m==0)
	{
		if(n%2) printf("%d",g^1);
		else printf("%d",g);
		return 0;
	}
	fac[0]=1;
	for(int i=1;i<=n+m;i++)
	{
		fac[i]=fac[i-1]*i%p;
	}
	inv[m+n]=qpow(fac[n+m],p-2);
	for(int i=m+n-1;i>=0;i--)
	{
		inv[i]=inv[i+1]*(i+1)%p;
	}
	for(int i=0;i<=n;i++)
	{
		if(i%2!=g) continue;
		ans=(ans+C(n+m-i-1,m-1))%p;
	}
	if(m==1)
	{
		if(n%2==g) ans=(ans-1+p)%p;
		else ans=(ans+1)%p;
	}
	printf("%lld",ans);
	return 0;
}

Increase Sequence

某次考试的题,可是依然不会

设区间数为cnt

  • \(|a_i-a_{i-1}|>1\) 则无解
  • \(a_i-a_{i-1}=-1\) 则需要开一段区间,cnt++
  • \(a_i-a_{i-1}=1\) 则需要闭合一段区间,方案数cnt
  • \(a_i-a_{i-1}=0\) 则分2种情况,一种是不操作,一种是先闭合一段区间,再新开一段,方案数cnt+1

代码:

#include<cstdio>
#define ll long long
using namespace std;
const int p=1e9+7;
int a[2002],n,h,c[2005];
ll ans,cnt;
int main()
{
	scanf("%d%d",&n,&h);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		a[i]=h-a[i];
	}
	for(int i=1;i<=n+1;i++)
	{
		c[i]=a[i]-a[i-1];
	}
	ans=1;
	for(int i=1;i<=n+1;i++)
	{
	//	if(a[i]>h) ans=0;
		if(c[i]>1||c[i]<-1) ans=0;
		if(c[i]==-1)
		{
			ans=ans*cnt%p;
			cnt--;
		}
		if(c[i]==1)
		{
			cnt++;
		}
		if(c[i]==0)
		{
			ans=ans*(cnt+1)%p;
		}
	}
	//if(cnt) ans=0;
	printf("%lld",ans);
	return 0;
}

Gerald and Giant Chess

很明显,总移动方式为\(C_{x+y-2}^{x-1}\),但其中有一些不合法,所以设\(f_i\)表示走到第i个黑格,且前面路径都合法的方案数

\(x_i\ge x_j\),\(y_i\ge y_j\),则要减去i到j的方案数,即\(f_j\times C_{x_i+y_i-x_j-y_j}^{x_i-x_j}\)

代码:

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int N=2e5,p=1e9+7;
int h,w,n;
ll fac[N+5],inv[N+5],f[2005];
struct node{
	int x,y;
}a[2005];
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%p;
		x=x*x%p;
		y>>=1;
	}
	return res;
}
bool cmp(node x,node y)
{
	if(x.x==y.x)return x.y<y.y;
	return x.x<y.x;
}
ll C(int m,int n)
{
	return fac[n]*inv[m]%p*inv[n-m]%p;
}
int main()
{
	scanf("%d%d%d",&h,&w,&n);
	fac[0]=1;
	for(int i=1;i<=N;i++)
	{
		fac[i]=fac[i-1]*i%p;
	}
	inv[N]=qpow(fac[N],p-2);
	for(int i=N-1;i>=0;i--)
	{
		inv[i]=inv[i+1]*(i+1)%p;
	}
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&a[i].x,&a[i].y);
	}
	sort(a+1,a+n+1,cmp);
	n++;
	a[n].x=h,a[n].y=w;
	for(int i=1;i<=n;i++)
	{
		f[i]=C(a[i].x-1,a[i].x+a[i].y-2);
		for(int j=1;j<i;j++)
		{
			if(a[i].y>=a[j].y)
			{
				f[i]=(f[i]-f[j]*C(a[i].x-a[j].x,a[i].x+a[i].y-a[j].x-a[j].y)%p+p)%p;
			}
		}
	}
	printf("%lld",f[n]);
	return 0;
}

Pluses everywhere

考虑dp,设\(dp_{i,j}\)表示在i之前有一个+,目前共用了j个加号,时间复杂度\(O(n^2 k)\)

考虑单独计算每一个\(a_i\)的贡献,可以发现,若最近的加号在\(a_{i+j}\)后面,则方案数为\(a_i\times 10^j\times C_{n-i-j-1}^{m-1}\)

所以考虑枚举j

有一个特例,\(a_{n-j+1}\)的贡献为\(a_{n-j+1}\times 10^j\times C_{m}^{n-j}\)

总方案数为:

\[\sum_{i=1}^{n-k} (10^{i-1}\times (\sum_{j=1}^{n-i}a_j \times C_{n-i-1}^{m-1}+a_{n-i+1}\times C_{n-i}^{m})) \]

代码:

#include<cstdio>
#include<string>
#include<iostream>
#define ll long long
using namespace std;
const int p=1e9+7;
int n,k;
string s;
ll fac[100005],inv[100005],pre[100005];
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%p;
		x=x*x%p;
		y>>=1;
	}
	return res;
}
ll C(int n,int m)
{
	return fac[n]*inv[m]%p*inv[n-m]%p;
}
int main()
{
	scanf("%d%d",&n,&k);
	cin>>s;
	fac[0]=1;
	s=" "+s;
	for(int i=1;i<=n;i++)
	{
		fac[i]=fac[i-1]*i%p;
		pre[i]=(pre[i-1]+s[i]-'0')%p;
	}
	inv[n]=qpow(fac[n],p-2);
	for(int i=n-1;i>=0;i--)
	{
		inv[i]=inv[i+1]*(i+1)%p;
	}
	ll q=1,ans=0;
	for(int i=1;i<=n-k;i++)
	{
		ans=(ans+q*(pre[n-i]*C(n-1-i,k-1)%p+(s[n-i+1]-'0')*C(n-i,k)%p)%p)%p;
		q=q*10%p;
	}
	printf("%lld",ans);
	return 0;
}

Special Matrices

\(dp_{i,j,k}\)表示目前是第i行,有j列须放1个,k列须放2个,则:

\[dp_{i,j,k}=dp_{i-1,j-2,k}+dp_{i-1,j+2,k-2}+dp_{i-1,j-1,k-1} \]

滚动数组优化即可

代码:

#include<cstdio>
#include<iostream>
#include<string>
#include<cstring>
#define ll long long
using namespace std;
int n,m,p,l[505];
ll dp[2][505][505];
int main()
{
	scanf("%d%d%d",&n,&m,&p);
	for(int i=1;i<=n;i++) l[i]=2;
	for(int i=1;i<=m;i++)
	{
		string s;
		cin>>s;
		s=" "+s;
		int tmp=0;
		for(int j=1;j<=n;j++)
		{
			if(s[j]=='1')
			{
				l[j]--;
				tmp++;
			}
		}
		if(tmp>2)
		{
			printf("0");
			return 0;
		}
	}
	int cnt1=0,cnt2=0;
	for(int i=1;i<=n;i++)
	{
		if(l[i]<0) 
		{
			printf("0");
			return 0;
		}
		if(l[i]==1) cnt1++;
		if(l[i]==2) cnt2++;
	//	printf("%d ",l[i]);
	}
//	printf("\n");
	int now=0,lst=1;
	dp[0][cnt1][cnt2]=1;
	for(int i=m+1;i<=n;i++)
	{
		now=now^1,lst=lst^1;
		memset(dp[now],0,sizeof(dp[now]));
		for(int j=0;j<=n;j++)
		{
			for(int k=0;k<=n;k++)
			{
				if(dp[lst][j][k]==0) continue;
			//	printf("%d %d %d\n",i,j,k);
				if(k>=1&&j>=1) dp[now][j][k-1]=(dp[now][j][k-1]+dp[lst][j][k]*j*k%p)%p;
				if(k>=2) dp[now][j+2][k-2]=(dp[now][j+2][k-2]+dp[lst][j][k]*k*(k-1)/2)%p;
				if(j>=2) dp[now][j-2][k]=(dp[now][j-2][k]+dp[lst][j][k]*j*(j-1)/2)%p;
			}
		}
	}
	printf("%lld",dp[now][0][0]);
	return 0;
}

hdu5396Expression

区间dp,设\(dp_{i,j}\)表示i~j范围内的结果之和,设断点为k

若符号为*,则\(dp_{i,j}=dp_{i,k}\times dp_{k,j}\),可以展开证明正确性

若符号为+,则\(dp_{i,j}=dp_{i,k}\times (j-k-1)!+dp_{i,k}\times (k-i)!\)

若符号为-,则\(dp_{i,j}=dp_{i,k}\times (j-k-1)!-dp_{i,k}\times (k-i)!\)

代码:

#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#define ll long long
using namespace std;
const int p=1e9+7;
int n,a[105];
string s;
ll dp[105][105],fac[105],inv[105];
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%p;
		x=x*x%p;
		y>>=1; 
	}
	return res;
}
ll C(int n,int m)
{
	return fac[n]*inv[m]%p*inv[n-m]%p;
}
int main()
{
	fac[0]=1;
	for(int i=1;i<=100;i++)
	{
		fac[i]=fac[i-1]*i%p;
	}
	inv[100]=qpow(fac[100],p-2);
	for(int i=99;i>=0;i--)
	{
		inv[i]=inv[i+1]*(i+1)%p;
	}
	while(cin>>n)
	{
		memset(dp,0,sizeof(dp));
	//		printf("%d %d\n",fac[0],inv[0]); 
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			dp[i][i]=a[i];
	//		printf("%d %d\n",fac[i],inv[i]); 
		}
		cin>>s;
		s=" "+s;
		for(int l=2;l<=n;l++)
		{
			for(int i=1;i+l-1<=n;i++)
			{
				int j=i+l-1;
				for(int k=i;k<j;k++)
				{
					ll tmp=0;
					if(s[k]=='*')
					{
						tmp=(dp[i][k]*dp[k+1][j])%p;
					}
					if(s[k]=='+')
					{
						tmp=(dp[i][k]*fac[j-k-1]%p+dp[k+1][j]*fac[k-i]%p)%p;
					}
					if(s[k]=='-')
					{
						tmp=(dp[i][k]*fac[j-k-1]%p-dp[k+1][j]*fac[k-i]%p)%p;
					}
					dp[i][j]=(dp[i][j]+tmp*C(j-i-1,k-i)%p)%p;
				//	printf("%lld ",C(j-i-1,k-i));
				}
			}
		}
		printf("%lld\n",(dp[1][n]+p)%p);
	}
	return 0;
}

hdu4661 Message Passing

可以发现,先把消息传给同一个人,再由他传给所有人效率最高

因为这是一颗树,一个图与其反图的拓扑序数量是一样的,考虑将信息都传给根,设\(dp_i\)为i子树的方案数

\[dp_{i}=\prod dp_{son(i)}\times size_i! \div \prod size_{son(i)}! \]

但是对于每个根都跑一遍dfs,显然会T,考虑up and down

设t为fa去掉u子树后的树,则:\(dp_{fa}=dp_t\times dp_u \times C_{n-1}^{size_u}\)

当u为根时,合并u子树与t,则:\(dp1_{u}=dp_u\times dp_t\times C_{n-1}^{size_u-1}\)

联立得:\(dp1_u=dp1_{fa}\times size_u\div(n-size_u)\)

代码:

#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int p=1e9+7,N=1e6;
int T,n,head[N+5],edgenum;
struct edge{
	int to,nxt;
}e[2000005];
ll fac[N+5],inv[N+5];
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%p;
		x=x*x%p;
		y>>=1;
	}
	return res;
}
void add_edge(int u,int v)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	head[u]=edgenum;
}
int siz[N+5];
ll dp[N+5],dp2[N+5],ans;
void init()
{
	edgenum=ans=0;
	memset(head,0,sizeof(head));
}
void dfs(int u,int fa)
{
	siz[u]=dp[u]=1;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs(v,u);
		siz[u]+=siz[v];
		dp[u]=dp[u]*dp[v]%p*inv[siz[v]]%p;
	}
	dp[u]=dp[u]*fac[siz[u]-1]%p;
}
void dfs2(int u,int fa)
{
	if(u!=1)
	{
		dp2[u]=dp2[fa]*siz[u]%p*qpow(n-siz[u],p-2)%p;
	}
	ans=(ans+dp2[u]*dp2[u]%p)%p; 
//	printf("%d %lld %lld\n",u,dp2[u],dp[u]);
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs2(v,u);
	}
}
int main()
{
	scanf("%d",&T);
	fac[0]=1;
	for(int i=1;i<=N;i++)
	{
		fac[i]=fac[i-1]*i%p;
	}
	inv[N]=qpow(fac[N],p-2);
	for(int i=N-1;i>=0;i--)
	{
		inv[i]=inv[i+1]*(i+1)%p;
	}
	while(T--)
	{
		scanf("%d",&n);
		init();
		for(int i=1;i<n;i++)
		{
			int u,v;
			scanf("%d%d",&u,&v);
			add_edge(u,v);
			add_edge(v,u);
		}
		dfs(1,0);
		dp2[1]=dp[1];
		dfs2(1,0);
		printf("%lld\n",ans);
	}
	return 0;
}
posted @ 2024-03-05 19:23  wangsiqi2010916  阅读(20)  评论(1编辑  收藏  举报