Codeforces 486D Valid Sets:Tree dp【n遍O(n)的dp】

Leohh發表於2018-01-12

題目連結: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 }

 

相關文章