題目連結:http://codeforces.com/problemset/problem/461/B
題意:
給你一棵樹(編號從0到n-1,0為根節點),每個節點有黑白兩種顏色,其中黑色節點有k+1個。
現在讓你刪掉k條邊,使得這棵樹變成k+1個連通塊,並且要保證每個連通塊中有且僅有一個黑色節點。
問你刪邊的方案有多少種。
題解:
表示狀態:
dp[i][0/1] = numbers
表示在節點i所在的連通塊中有(1)或沒有(0)黑色節點時,節點i的子樹的刪邊方法數
因為總要保證每個連通塊中有且僅有一個黑點,所以最後一定刪了恰好k條邊,並不用記錄當前刪了多少邊。
找出答案:
ans = dp[0][1]
最終根所在連通塊中一定有且僅有一個黑點。
如何轉移:
將刪邊的過程反過來考慮。
將節點i連向它的兒子的邊一條條刪去,相當於:
i本身沒有兒子,然後將一棵棵子樹新增為它的兒子,同時保證合法。
那麼最終的方案取決於三個條件:
(1)i所在的連通塊(簡稱塊i)是否有黑點
(2)son所在的連通塊(簡稱塊son)是否有黑點
(3)是否刪去邊(i,son)
分情況討論:
(1)塊i有黑點
a. 塊son有黑點,此時只能將邊刪去,最終的塊i有黑點
b. 塊son全是白,此時只能保留這條邊,最終的塊i有黑點
(2)i是白色
a. 塊son有黑點,此時刪邊或不刪都可以:
I. 刪邊,最終的塊i全是白
II. 不刪,最終的塊i有黑點
b. 塊son全是白,此時只能保留這條邊,最終的塊i全是白
綜上:
dp[now][1] = dp[son][0]*dp[now][1] + dp[son][1]*(dp[now][1]+dp[now][0])
dp[now][0] = (dp[son][0]+dp[son][1])*dp[now][0]
邊界條件:
dp[i][c[i]]=1
AC Code:
#include <iostream> #include <stdio.h> #include <string.h> #include <vector> #define MAX_N 100005 #define MOD 1000000007 using namespace std; int n; int c[MAX_N]; long long dp[MAX_N][2]; vector<int> edge[MAX_N]; void read() { cin>>n; int x; for(int i=1;i<n;i++) { cin>>x; edge[x].push_back(i); } for(int i=0;i<n;i++) { cin>>c[i]; } } void dfs(int now) { dp[now][c[now]]=1; for(int i=0;i<edge[now].size();i++) { int temp=edge[now][i]; dfs(temp); long long blk=dp[now][1]; long long wht=dp[now][0]; dp[now][1]=(dp[temp][0]*blk+dp[temp][1]*(blk+wht))%MOD; dp[now][0]=(dp[temp][0]+dp[temp][1])*wht%MOD; } } void work() { dfs(0); cout<<dp[0][1]<<endl; } int main() { read(); work(); }