Codeforces 543D Road Improvement(树形DP + 乘法逆元)

时间:2021-03-29 03:22:46

题目大概说给一棵树,树的边一开始都是损坏的,要修复一些边,修复完后要满足各个点到根的路径上最多只有一条坏的边,现在以各个点为根分别求出修复边的方案数,其结果模1000000007。

不难联想到这题和HDU2196是一种类型的树形DP,因为它们都要分别求各个点的答案。然后解法也不难想:

  • dp0[u]表示只考虑以u结点为根的子树的方案数
  • dp1[u]表示u结点往上走,倒过来,以它父亲为根那部分的方案数

有了这两部分的结果,对于各个点u的答案就是dp0[u]*(dp1[u]+1)。这两部分求法如下,画画图比较好想:

  • 首先求出dp0,这个转移是:dp0[u]=∏(dp0[v]+1)(v是u的孩子),就是对于每个孩子为根的子树的情况总数的乘积,而其中每个孩子的情况总数还要加上一个父亲到孩子之间的边不修复、孩子的子树的边全部修复的情况。
  • 然后求出dp1,转移:求dp1[v],u是v的父亲,dp1[v]=dp0[u]/dp0[v]*(dp1[u]+1)。
  • 现在问题来了,求dp0[u]/dp0[v],注意到结果模1000000007是一个质数,一开始我用乘法逆元WA了,因为虽然1000000007是质数,但1000000007的倍数不与1000000007互质,模1000000007结果是0,这样就出问题了!
  • 本来我想改用线段树做,不过队友提醒说可以分情况讨论,如果不存在与1000000007不互质的数直接逆元搞,存在两个以上不与1000000007互质的数那结果就是0,一个的话。。。。。我就不多说了。
 #include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 222222
struct Edge{
int v,next;
}edge[MAXN<<];
int NE,head[MAXN];
void addEdge(int u,int v){
edge[NE].v=v; edge[NE].next=head[u];
head[u]=NE++;
}
long long d[][MAXN];
long long ine(long long a){
long long res=;
int n=-;
while(n){
if(n&){
res*=a;
res%=;
}
a*=a;
a%=;
n>>=;
}
return res;
}
void dp0(int u,int fa){
long long res=;
for(int i=head[u]; i!=-; i=edge[i].next){
int v=edge[i].v;
if(v==fa) continue;
dp0(v,u);
res*=d[][v]+;
res%=;
}
d[][u]=res;
}
void dp1(int u,int fa){
int cnt=;
long long tot=;
for(int i=head[u]; i!=-; i=edge[i].next){
int v=edge[i].v;
if(v==fa) continue;
if((d[][v]+)%==) ++cnt;
else{
tot*=d[][v]+;
tot%=;
}
}
for(int i=head[u]; i!=-; i=edge[i].next){
int v=edge[i].v;
if(v==fa) continue;
if(cnt){
if((d[][v]+)%== && cnt==){
d[][v]=tot;
}else d[][v]=;
}else{
d[][v]=d[][u]*ine((d[][v]+)%);
d[][v]%=;
}
d[][v]*=d[][u]+;
d[][v]%=;
dp1(v,u);
}
}
int main(){
memset(head,-,sizeof(head));
int n,a;
scanf("%d",&n);
for(int i=; i<=n; ++i){
scanf("%d",&a);
addEdge(a,i);
addEdge(i,a);
}
dp0(,);
dp1(,);
for(int i=; i<=n; ++i){
printf("%lld ",d[][i]*(d[][i]+)%);
}
return ;
}