AtCoder Beginner Contest 160 F Distributing Integers 换根dp+排列组合

AtCoder Beginner Contest 160   比赛人数9747  快,比赛开始后3分钟看到所有题

AtCoder Beginner Contest 160 F  Distributing Integers  换根dp+排列组合

总目录详见https://blog.csdn.net/mrcrack/article/details/104454762

在线测评地址https://atcoder.jp/contests/abc160/tasks/abc160_f

换根dp 若不明白,可看此文[codeforces 1187E] Tree Painting 换根dp

阶乘逆元 若不会计算,可看此文阶乘逆元 三种 快速 方法 费马小定理 递推 线性

1.下图以3为根节点,种类数840的计算由来

1.1如下,需从叶节点出发开始计算,利用组合数学的乘法原理,请注意1这个数据已经被根节点3占用了

1.2节点3与节点6合并,共有6个数据可用,扣除节点3占用的数据1,还剩5个数据,分配给节点6,7,8组团,即为C(5,3)由来

 1.3节点3与节点2合并,共有8个数据可用,扣除节点3占用的数据1,还剩7个数据,分配给节点1,2组团,即为C(7,2)由来

2.以样例4为例,展示数据暴力生成过程,若对数据生成过程不清楚,可以看AC代码中dfs1部分的内容

2.1以1为根,对应的数据生成过程如下

pre[u]=pre[v]*pre[u]*C(sz[u]-1,sz[v]);

pre[u]是以u为根的子树的安排方式数,sz[u]是以u为根的子树的节点数

sz[8]=1 pre[8]=1 sz[6]=2 pre[6]=1
sz[7]=1 pre[7]=1 sz[6]=3 pre[6]=2
sz[6]=3 pre[6]=2 sz[3]=4 pre[3]=2
sz[5]=1 pre[5]=1 sz[3]=5 pre[3]=8
sz[4]=1 pre[4]=1 sz[3]=6 pre[3]=40
sz[3]=6 pre[3]=40 sz[2]=7 pre[2]=40
sz[2]=7 pre[2]=40 sz[1]=8 pre[1]=40
sz[1]=8
sz[2]=7
sz[3]=6
sz[4]=1
sz[5]=1
sz[6]=3
sz[7]=1
sz[8]=1
pre[1]=40
pre[2]=40
pre[3]=40
pre[4]=1
pre[5]=1
pre[6]=2
pre[7]=1
pre[8]=1

2.2以2为根,对应的数据生成过程如下

sz[8]=1 pre[8]=1 sz[6]=2 pre[6]=1
sz[7]=1 pre[7]=1 sz[6]=3 pre[6]=2
sz[6]=3 pre[6]=2 sz[3]=4 pre[3]=2
sz[5]=1 pre[5]=1 sz[3]=5 pre[3]=8
sz[4]=1 pre[4]=1 sz[3]=6 pre[3]=40
sz[3]=6 pre[3]=40 sz[2]=7 pre[2]=40
sz[1]=1 pre[1]=1 sz[2]=8 pre[2]=280
sz[1]=1
sz[2]=8
sz[3]=6
sz[4]=1
sz[5]=1
sz[6]=3
sz[7]=1
sz[8]=1
pre[1]=1
pre[2]=280
pre[3]=40
pre[4]=1
pre[5]=1
pre[6]=2
pre[7]=1
pre[8]=1

2.3以3为根,对应的数据生成过程如下

sz[8]=1 pre[8]=1 sz[6]=2 pre[6]=1
sz[7]=1 pre[7]=1 sz[6]=3 pre[6]=2
sz[6]=3 pre[6]=2 sz[3]=4 pre[3]=2
sz[5]=1 pre[5]=1 sz[3]=5 pre[3]=8
sz[4]=1 pre[4]=1 sz[3]=6 pre[3]=40
sz[1]=1 pre[1]=1 sz[2]=2 pre[2]=1
sz[2]=2 pre[2]=1 sz[3]=8 pre[3]=840
sz[1]=1
sz[2]=2
sz[3]=8
sz[4]=1
sz[5]=1
sz[6]=3
sz[7]=1
sz[8]=1
pre[1]=1
pre[2]=1
pre[3]=840
pre[4]=1
pre[5]=1
pre[6]=2
pre[7]=1
pre[8]=1

2.4以4为根,对应的数据生成过程如下

sz[8]=1 pre[8]=1 sz[6]=2 pre[6]=1
sz[7]=1 pre[7]=1 sz[6]=3 pre[6]=2
sz[6]=3 pre[6]=2 sz[3]=4 pre[3]=2
sz[5]=1 pre[5]=1 sz[3]=5 pre[3]=8
sz[1]=1 pre[1]=1 sz[2]=2 pre[2]=1
sz[2]=2 pre[2]=1 sz[3]=7 pre[3]=120
sz[3]=7 pre[3]=120 sz[4]=8 pre[4]=120
sz[1]=1
sz[2]=2
sz[3]=7
sz[4]=8
sz[5]=1
sz[6]=3
sz[7]=1
sz[8]=1
pre[1]=1
pre[2]=1
pre[3]=120
pre[4]=120
pre[5]=1
pre[6]=2
pre[7]=1
pre[8]=1

2.5以5为根,对应的数据生成过程如下

sz[8]=1 pre[8]=1 sz[6]=2 pre[6]=1
sz[7]=1 pre[7]=1 sz[6]=3 pre[6]=2
sz[6]=3 pre[6]=2 sz[3]=4 pre[3]=2
sz[4]=1 pre[4]=1 sz[3]=5 pre[3]=8
sz[1]=1 pre[1]=1 sz[2]=2 pre[2]=1
sz[2]=2 pre[2]=1 sz[3]=7 pre[3]=120
sz[3]=7 pre[3]=120 sz[5]=8 pre[5]=120
sz[1]=1
sz[2]=2
sz[3]=7
sz[4]=1
sz[5]=8
sz[6]=3
sz[7]=1
sz[8]=1
pre[1]=1
pre[2]=1
pre[3]=120
pre[4]=1
pre[5]=120
pre[6]=2
pre[7]=1
pre[8]=1

2.6以6为根,对应的数据生成过程如下

sz[8]=1 pre[8]=1 sz[6]=2 pre[6]=1
sz[7]=1 pre[7]=1 sz[6]=3 pre[6]=2
sz[5]=1 pre[5]=1 sz[3]=2 pre[3]=1
sz[4]=1 pre[4]=1 sz[3]=3 pre[3]=2
sz[1]=1 pre[1]=1 sz[2]=2 pre[2]=1
sz[2]=2 pre[2]=1 sz[3]=5 pre[3]=12
sz[3]=5 pre[3]=12 sz[6]=8 pre[6]=504
sz[1]=1
sz[2]=2
sz[3]=5
sz[4]=1
sz[5]=1
sz[6]=8
sz[7]=1
sz[8]=1
pre[1]=1
pre[2]=1
pre[3]=12
pre[4]=1
pre[5]=1
pre[6]=504
pre[7]=1
pre[8]=1

2.7以7为根,对应的数据生成过程如下

sz[8]=1 pre[8]=1 sz[6]=2 pre[6]=1
sz[5]=1 pre[5]=1 sz[3]=2 pre[3]=1
sz[4]=1 pre[4]=1 sz[3]=3 pre[3]=2
sz[1]=1 pre[1]=1 sz[2]=2 pre[2]=1
sz[2]=2 pre[2]=1 sz[3]=5 pre[3]=12
sz[3]=5 pre[3]=12 sz[6]=7 pre[6]=72
sz[6]=7 pre[6]=72 sz[7]=8 pre[7]=72
sz[1]=1
sz[2]=2
sz[3]=5
sz[4]=1
sz[5]=1
sz[6]=7
sz[7]=8
sz[8]=1
pre[1]=1
pre[2]=1
pre[3]=12
pre[4]=1
pre[5]=1
pre[6]=72
pre[7]=72
pre[8]=1

2.8以8为根,对应的数据生成过程如下

sz[7]=1 pre[7]=1 sz[6]=2 pre[6]=1
sz[5]=1 pre[5]=1 sz[3]=2 pre[3]=1
sz[4]=1 pre[4]=1 sz[3]=3 pre[3]=2
sz[1]=1 pre[1]=1 sz[2]=2 pre[2]=1
sz[2]=2 pre[2]=1 sz[3]=5 pre[3]=12
sz[3]=5 pre[3]=12 sz[6]=7 pre[6]=72
sz[6]=7 pre[6]=72 sz[8]=8 pre[8]=72
sz[1]=1
sz[2]=2
sz[3]=5
sz[4]=1
sz[5]=1
sz[6]=7
sz[7]=1
sz[8]=8
pre[1]=1
pre[2]=1
pre[3]=12
pre[4]=1
pre[5]=1
pre[6]=72
pre[7]=1
pre[8]=72

3.以样例4为例,展示数据 换根dp 生成过程

3.1以1为根,对应的数据生成过程如下

pre[u]是以u为根的子树的安排方式数,sz[u]是以u为根的子树的节点数

sz[8]=1 pre[8]=1 sz[6]=2 pre[6]=1
sz[7]=1 pre[7]=1 sz[6]=3 pre[6]=2
sz[6]=3 pre[6]=2 sz[3]=4 pre[3]=2
sz[5]=1 pre[5]=1 sz[3]=5 pre[3]=8
sz[4]=1 pre[4]=1 sz[3]=6 pre[3]=40
sz[3]=6 pre[3]=40 sz[2]=7 pre[2]=40
sz[2]=7 pre[2]=40 sz[1]=8 pre[1]=40
sz[1]=8
sz[2]=7
sz[3]=6
sz[4]=1
sz[5]=1
sz[6]=3
sz[7]=1
sz[8]=1
pre[1]=40
pre[2]=40
pre[3]=40
pre[4]=1
pre[5]=1
pre[6]=2
pre[7]=1
pre[8]=1

v是下一次搜索的根,u是当前的根,tp[v]代表在以v为根时,pre[u]的值。

tp[v]=ans[u]/pre[v]/C(n-1,sz[v]);

ans[u]=tp[v]*pre[u]*C(n-1,n-sz[u]);

ans[1]=40 u=2 fa=1
tp[2]=ans[1]/pre[2]/C(n-1,sz[1])=40/40/C(7,7)=1
ans[2]=tp[2]*pre[2]*C(n-1,n-sz[2])=1*40*C(7,1)=280

ans[2]=280 u=3 fa=2
tp[3]=ans[2]/pre[3]/C(n-1,sz[2])=280/40/C(7,6)=1
ans[3]=tp[3]*pre[3]*C(n-1,n-sz[3])=1*40*C(7,2)=840

u=6 fa=3
u=8 fa=6
u=7 fa=6
u=5 fa=3
u=4 fa=3
40
280
840
120
120
504
72
72

思路同https://www.cnblogs.com/JohnRan/p/12591555.html

F. 给你一颗n个节点的树,依次放入1-n,放置的条件是这条边的另一个点已经放置了数字,求把1分别放置在节点1-n的方案数。

  因为要求把1放在1-n号节点上的方案数,明示了换根dp(或许还有其他做法,但我不会嘿嘿。先考虑以把1放在一号节点的时候怎么数操作数。

  设pre[v]是以v为根的子树的安排方式数。这个时候就是一个简单的乘法原理,先选再放进子树安排。第一次dfs就能处理出所有的pre[]。在换根的时候

  我们首先要考虑到以u的儿子v为根的时候,我们首先要得到u作为v子树的pre[u]。这个显然(可能需要在纸上稍微写一下,原理就是先选再排)是ans[u]/(pre[v]*C(n-1,sz[v])),知道这个之后,得到ans[v]不过就是为u

  这个子树重新选取一些数字而已,这个显然是C(n-1,n-sz[v])种可能。根据计算u答案的方法,直接套用到v上即可,具体见代码。

AC代码如下

#include <cstdio>
#include <algorithm>
#define LL long long
#define mod 1000000007
#define maxn 200010
using namespace std;
LL fact[maxn],inv[maxn];
int n,head[maxn],cnt,sz[maxn];//sz[u]是以u为根的子树的节点数
LL pre[maxn],ans[maxn],tp[maxn];//pre[u]是以u为根的子树的安排方式数
struct node{
	int to,next;
}e[maxn<<1];
void add_edge(int u,int v){//邻接表
	cnt++,e[cnt].to=v,e[cnt].next=head[u],head[u]=cnt;
}
LL quick_pow(LL a,LL b){//快速幂
	LL ans=1;
	while(b){
		if(b&1)ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}
void init(){
	int i,u,v;
	scanf("%d",&n);
	for(i=1;i<n;i++){
		scanf("%d%d",&u,&v);
		add_edge(u,v),add_edge(v,u);//无向图
	}
	fact[0]=1;
	for(i=1;i<=n;i++)fact[i]=fact[i-1]*i%mod;
	inv[n]=quick_pow(fact[n],mod-2);
	for(i=n-1;i>=0;i--)inv[i]=inv[i+1]*(i+1)%mod;
}
LL C(int a,int b){//组合数计算
	return fact[a]*inv[a-b]%mod*inv[b]%mod;
}
void dfs1(int u,int fa){
	int b,v;
	sz[u]=1,pre[u]=1;
	for(b=head[u];b;b=e[b].next){
		v=e[b].to;
		if(v==fa)continue;
		dfs1(v,u);
		sz[u]+=sz[v];
		pre[u]=pre[v]*pre[u]%mod*C(sz[u]-1,sz[v])%mod;
	}
}
void dfs2(int u,int fa){//换根dp
	int b,v;
	ans[u]=tp[u]*pre[u]%mod*C(n-1,n-sz[u])%mod;
	for(b=head[u];b;b=e[b].next){
		v=e[b].to;
		if(v==fa)continue;
		tp[v]=ans[u]*quick_pow(pre[v]*C(n-1,sz[v])%mod,mod-2)%mod;//v是下一次搜索的根,u是当前的根,tp[v]代表在以v为根时,pre[u]的值。
		dfs2(v,u);
	}
}
int main(){
	init();
	dfs1(1,0);
	tp[1]=1;
	dfs2(1,0);
	for(int i=1;i<=n;i++)printf("%lld\n",ans[i]);
	return 0;
}
发布了660 篇原创文章 · 获赞 562 · 访问量 48万+

猜你喜欢

转载自blog.csdn.net/mrcrack/article/details/105207929
今日推荐