Codeforces 486D Valid Sets:Tree dp【n遍O(n)的dp】
題目鏈接:http://codeforces.com/problemset/problem/486/D
題意:
給你一棵樹,n個節點,每個節點的點權為a[i]。
問你有多少個連通子圖,使得子圖中的max(a[i]) - min(a[i]) <= d。
ps.連通子圖的定義:
如果一個點集V為一個連通子圖,則對於任意兩點a,b∈V,有a到b路徑上的所有點u∈V。
題解:
因為要保證max(a[i]) - min(a[i]) <= d,所以可以人為地選出一個點rt作為點權最大的點。
這樣在求以rt為最大點的連通子圖個數時,只用考慮點權不超過a[rt]的點。
然而如果有兩個點i,j的點權相同,分別以i,j作為最大點的兩堆子圖中會有重復。
所以可以定義一下:當以rt作為最大點時,所有點權與a[rt]相等的點,它們的節點編號id[i]必須大於id[rt]。
這樣就能避免重復了。
所以最終答案 = ∑(以i為最大點的連通子圖個數)
求以i為最大點的連通子圖個數,只需一遍O(n)的dp就行。
註意,當前這是一棵無根樹。以下所說的“i的子樹”意思是:從i出發往葉子方向的那一堆點。
假設當前以rt作為最大點。
則加入連通子圖的點i必須滿足:
(1)a[rt]-a[i]<=d(保證滿足題目條件)
(2)a[i]<=a[rt](保證a[rt]為最大點)
(3)如果a[i]==a[rt] && i!=rt,則要滿足rt<i(避免重復計數)
表示狀態:
dp[i] = numbers
表示節點i肯定要選,i的子樹所構成的合法連通子圖個數
找出答案:
每次ans += dp[rt]
如何轉移:
dp[i] = ∏ (dp[son]+1)
邊界條件:
對於葉子結點leaf: dp[leaf] = 1
總復雜度O(n^2)。
AC Code:
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 #include <vector> 5 #define MAX_N 2005 6 #define MOD 1000000007 7 8 using namespace std; 9 10 int n,d; 11 int a[MAX_N]; 12 vector<int> edge[MAX_N]; 13 14 void read() 15 { 16 cin>>d>>n; 17 for(int i=1;i<=n;i++) cin>>a[i]; 18 int x,y; 19 for(int i=1;i<n;i++) 20 { 21 cin>>x>>y; 22 edge[x].push_back(y); 23 edge[y].push_back(x); 24 } 25 } 26 27 long long dfs(int now,int p,int rt,int mx) 28 { 29 if(mx-a[now]>d || a[now]>mx || (a[now]==mx && p!=-1 && now<rt)) return 0; 30 long long res=1; 31 for(int i=0;i<edge[now].size();i++) 32 { 33 int temp=edge[now][i]; 34 if(temp!=p) res=res*(dfs(temp,now,rt,mx)+1)%MOD; 35 } 36 return res; 37 } 38 39 void work() 40 { 41 long long ans=0; 42 for(int i=1;i<=n;i++) ans=(ans+dfs(i,-1,i,a[i]))%MOD; 43 cout<<ans<<endl; 44 } 45 46 int main() 47 { 48 read(); 49 work(); 50 }
Codeforces 486D Valid Sets:Tree dp【n遍O(n)的dp】