UVA1436 - Counting heaps

链接

https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=4182

题解

从树形 d p dp dp的角度去考虑这道题
先求出每棵子树的大小 s i z e i size_i sizei,并求出将 1... s i z e i 1...size_i 1...sizei按照题目所讲的规则给这棵子树编号的方案数为 f i f_i fi
那么 f i = ( s i z e i − 1 ) ! ∏ j ∈ s o n i s i z e j ∏ j ∈ s o n i f j f_i={(size_i-1)!\over\prod_{j\in son_i}size_j}\prod_{j\in son_i}f_j fi=jsonisizej(sizei1)!jsonifj
化简一下,发现最终 a n s = f 1 = ( n − 1 ) ! ∏ i = 2 n s i z e i ans=f_1={(n-1)!\over \prod_{i=2}^nsize_i} ans=f1=i=2nsizei(n1)!
由于 m m m不是素数,所以分母的积没法直接求逆元
先将 m m m拆分, m = p 1 q 1 p 2 q 2 . . . p k q k m=p_1^{q_1}p_2^{q_2}...p_k^{q_k} m=p1q1p2q2...pkqk
对于每一个 p i p_i pi
我先将分子分母中的 p p p都提取出来,剩下的直接用逆元计算,最后再单独算 p p p的幂
对于不同的 p p p,使用中国剩余定理合并即可

代码

//中国余数定理
#include <bits/stdc++.h>
#define maxn 500010
#define cl(x) memset(x,0,sizeof(x))
#define ll long long
using namespace std;
ll N, M, size[maxn], a[maxn], m[maxn], prime[maxn], mark[maxn], p[maxn], q[maxn], tot, etot, to[maxn], head[maxn], nex[maxn];
void shai()
{
	ll i, j;
	for(i=2;i<maxn;i++)
	{
		if(!mark[i])prime[++*prime]=i;
		for(j=1;j<=*prime and i*prime[j]<maxn;j++)
		{
			mark[i*prime[j]]=1;
			if(i%prime[j]==0)break;
		}
	}
}
ll read(ll x=0)
{
	ll c, f=1;
	for(c=getchar();!isdigit(c);c=getchar())if(c=='-')f=-1;
	for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-48;
	return f*x;
}
void adde(ll a, ll b){to[++etot]=b;nex[etot]=head[a];head[a]=etot;}
void dfs(ll pos, ll pre)
{
	ll p;
	size[pos]=1;
	for(p=head[pos];p;p=nex[p])
		if(to[p]!=pre)dfs(to[p],pos), size[pos]+=size[to[p]];
}
ll fastpow(ll a, ll b, ll mod)
{
	ll t=a, ans=1;
	for(;b;b>>=1,t=t*t%mod)if(b&1)ans=ans*t%mod;
	return ans;
}
void init()
{
	cl(head), cl(nex), tot=etot=0;
	ll i, t, f;
	N=read(), M=read();
	for(i=2;i<=N;i++)adde(read(),i);
	dfs(1,0);
	tot=0;
	for(i=1,t=M;t>1 and i<=*prime and prime[i]*prime[i]<=t;i++)
		if(t%prime[i]==0)
		{
			tot++, p[tot]=prime[i], q[tot]=0;
			while(t%p[tot]==0)t/=prime[i], q[tot]++;
		}
	if(t>1)p[++tot]=t, q[tot]=1;
}
void exgcd(ll a, ll b, ll &x, ll &y)
{
	if(!b){x=1,y=0;return;}
	ll xx, yy;
	exgcd(b,a%b,xx,yy);
	x=yy, y=xx-a/b*yy;
}
ll inv(ll a, ll p)
{
	ll x, y;
	exgcd(a,p,x,y);
	return (x+p)%p;
}
void work()
{
	ll i, j, k, cnt, t;
	for(i=1;i<=tot;i++)
	{
		cnt=0;
		a[i]=1;
		m[i]=fastpow(p[i],q[i],M+1);
		for(j=1;j<N;j++)
		{
			for(t=j;t%p[i]==0;t/=p[i],cnt++);
			a[i]=a[i]*t%m[i];
		}
		for(j=2;j<=N;j++)
		{
			for(t=size[j];t%p[i]==0;t/=p[i],cnt--);
			a[i]=a[i]*inv(t,m[i])%m[i];
		
		}
		a[i]=a[i]*fastpow(p[i],cnt,m[i])%m[i];
	}
}
ll crt(ll *a, ll *m, ll n)
{
	ll M=1, ans=0, i;
	for(i=1;i<=n;i++)M*=m[i];
	for(i=1;i<=n;i++)ans=(ans+a[i]*(M/m[i])%M*inv(M/m[i],m[i]))%M;
	return ans;
}
int main()
{
	shai();
	ll T=read();
	while(T--)init(), work(), printf("%lld\n",crt(a,m,tot));
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值