[提高组集训2021] 退钱

一、题目

有一棵 \(n\) 个点的树,根为 \(1\),每个点都有一个初始为 \(1\) 的标记值 \(s_i\),对点 \(x\) 进行操作表示把 \(x\) 的祖先中深度最小并且 \(s_i=1\)\(i\) 置为 \(0\),每个叶子有一个操作次数 \(a_i\),问有多少种不同的操作序列使得最后标记值全为 \(0\)

\(n\leq 10^6\)\(\sum a_i=n\),保证若 \(i\) 非叶子则 \(a_i=0\)

二、解法

我们自底向上考虑,对于每个子树,我们把操作分为两部分,第一部分是操作 \(u\) 的祖先,第二部分是操作 \(u\) 的子树,可以知道第一部分的操作全部在第二部分之前,这两者在操作序列上有着不可逾越的鸿沟!

\(dp[i]\) 表示子树 \(i\) 内的操作序列方案数,考虑怎么把子树内的信息合并上来,发现第一部分内部可以任意安排顺序,第二部分内部也可以任意安排顺序,所以做一个可重集的排列数即可。

三、总结

顺序并不需要一步到位,可以慢慢确定的嘛。

树的问题,可以整体考虑,也可以从小处入手,把问题归结到子树上。

//deep in my bone , straight from inside 
#include <cstdio>
const int M = 1000005;
const int MOD = 1e9+7;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,tot,f[M],a[M],dp[M],siz[M],re[M],fac[M],inv[M];
struct edge{int v,next;}e[M];
signed main()
{
	freopen("ball.in","r",stdin);
	freopen("ball.out","w",stdout);
	n=read();
	for(int i=2;i<=n;i++)
	{
		int j=read();
		e[++tot]=edge{i,f[j]},f[j]=tot;
	}
	fac[0]=inv[0]=inv[1]=1;
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		fac[i]=fac[i-1]*i%MOD;
	}
	for(int i=2;i<=n;i++)
		inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD; 
	for(int i=2;i<=n;i++)
		inv[i]=inv[i-1]*inv[i]%MOD;
	for(int u=n;u>=1;u--)
	{
		siz[u]=1;re[u]=a[u]-1;dp[u]=1;
		for(int i=f[u];i;i=e[i].next)
		{
			int v=e[i].v;
			siz[u]+=siz[v];
			re[u]+=re[v];
			dp[u]=dp[u]*dp[v]%MOD
			*inv[siz[v]]%MOD*inv[re[v]]%MOD;
		}
		if(re[u]<0) {puts("0");return 0;}
		dp[u]=dp[u]*fac[siz[u]-1]%MOD*fac[re[u]+1]%MOD;
		if(!f[u]) dp[u]=1;//leaf
	}
	printf("%lld\n",dp[1]);
}
posted @ 2021-10-03 21:53  C202044zxy  阅读(74)  评论(0编辑  收藏  举报