DP系列——树形DP(Codeforces543D-Road Improvement)

时间:2023-08-26 23:02:00

一、题目链接

  http://codeforces.com/problemset/problem/543/D

二、题意

  给一棵树,一开始所有路都是坏的。询问,以每个节点$i$为树根,要求从树根节点到其他每个节点的路径上都满足最多只有一条边是坏的。问有多少种修路方式。输出以每个节点$i$为根节点的满足条件的修路方式。结果对$10^9+7$取模。

三、思路

  假设$ans[i]$为节点$i$的答案。

  定$1$为总树根。$dp[i]$表示:以节点$i$为根的子树中的修路方式。易知,$dp[i] = \prod\limits_{j}(dp[j] + 1) \% (10^9+7)$,其中$j$为$i$的子节点。

  显然,$ans[1] = dp[1]$。当已知父节点$i$的$ans[i]$时,求节点$i$的子节点$j$的$ans[j]$,不难得到如下式子:

  \[ans[j] = dp[j] * (\frac{dp[i]}{dp[j]+1}+1)\]

  使用上述递推式,加上求$(dp[j]+1)$的逆元,能想到的样例基本都没错。提交后,会WA on 10。

  WA的原因是,当$dp[j]+1=10^9+7,dp[i]\%(10^9+7)=0$时,使用上述递推式会出问题。

  所以应该转换思路。上面这种思路的问题所在就是除法,那么,现在就要避免除法。设$dp2[i]$表示除去以节点$i$为根的子树后剩余的树中的修路方式。如图所示,红框中的树上所有的修路方式就是$dp2[i]$。

  有了$dp2$以后,就可以得到:$dp2[j] = (dp2[i] * \prod\limits_{k}(dp[k] + 1) + 1) \% (10^9 + 7), ans[j] = dp1[j] * dp2[j]$。其中,$j$为$i$的儿子节点,$k$为$j$的所有兄弟节点。而计算$\prod\limits_{k}(dp[k] + 1)$,需要预处理,否则会TLE在“星型树”的样例上。

  预处理出如下两个信息:

  (1)$vector<int> pre[MAXN]$。其中,$pre[i]$表示节点$i$的儿子节点的$dp$前缀积;即:$pre[i][j]$表示节点$i$的编号从$0$到$j$的儿子节点的$dp$前缀积;

  (2)$vector<int> suf[MAXN]$。其中,$suf[i]$表示节点$i$的儿子节点的$dp$后缀积。即:$suf[i][j]$表示节点$i$的编号从$j$到$suf[i].size()-1$的儿子节点的$dp$后缀积;

  因为出现了“前缀”和“后缀”的概念,所以,对于每个节点$i$而言,都需要把他的所有子节点按顺序编号。因此,树的存储结构不能再使用链式前向星的方式,而应该写成邻接表的形式。而且,要注意的是,建树时,不要建成双向图(树是特殊的图^_^),因为要对子节点编号。如果父节点又是子节点的子节点,那就乱了。

  然后,对于节点$x$的第$j$子节点$y$而言,有\[ans[y]=(dp[y] * dp2[x] * (pre[x][j-1] * suf[x][j+1]) + 1) \% (10^9 + 7)\] 这个地方要注意这些下标的意义:$x$和$y$是树中节点的编号,总根编号为$1$。$j$是节点$y$在节点$x$的所有子节点中的位置。如果节点$x$有三个节点,$y$是$x$的第$2$个节点,那么,$j$为$1$(因为下标从$0$开始嘛)。

  这里递推式比较多,使用LaTeX编辑时容易出现小错误,如发现,还请在评论区多多指正!

四、源代码

  

#include<bits/stdc++.h>
using namespace std;
#define pb(x) push_back(x)
#define MAXN 200010
#define MOD 1000000007
typedef long long LL;
vector<int> tree[MAXN], pre[MAXN], suf[MAXN];
LL n, dp1[MAXN], dp2[MAXN];

void dfs0(int root) {
    , sz = tree[root].size(); i < sz; ++i) {
        int to = tree[root][i];
        dfs0(to);
        dp1[root] = (dp1[root] * ((dp1[to] + ) % MOD)) % MOD;
        )pre[root].pb(pre[root].back() * (dp1[to] + ) % MOD);
        ) % MOD);
    }
    ; i >= ;--i){
        int to = tree[root][i];
        )suf[root].pb(suf[root].back() * (dp1[to] + ) % MOD);
        ) % MOD);
    }
    reverse(suf[root].begin(), suf[root].end());
}

void dfs1(int root) {
    , sz = tree[root].size(); i < sz; ++i) {
        int to = tree[root][i];
        LL ps = i ==  ?  : pre[root][i - ], ss = i == sz -  ?  : suf[root][i + ];
        dp2[to] = ((dp2[root] * ps % MOD) * ss + ) % MOD;
        dfs1(to);
    }
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("input.txt", "r", stdin);
#endif // ONLINE_JUDGE
    int from;
    scanf("%I64d", &n);
    ; i <= n; ++i)dp1[i] = , dp2[i] = ;
    ; i <= n; ++i) {
        scanf("%d", &from);
        tree[from].pb(i);
    }
    dfs0();
    dfs1();
    ; i <= n; ++i)printf("%I64d%c", dp1[i] * dp2[i] % MOD, i == n ? '\n' : ' ');
    ;
}