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;
}