1. 程式人生 > 實用技巧 >樹形dp小結

樹形dp小結

最近開了一個樹形專題,但是很多題笨比mwh都是看了題解才會的,所以自己掌握的並不好,打算做一些小的總結。會持續更新。

1.hdu2196Computer

原題連結http://acm.hdu.edu.cn/showproblem.php?pid=2196

題意:給出一棵樹,求離每個節點最遠的點的距離

是一道非常經典的樹形dp入門題。

首先選定一個結點為樹跟把這棵樹轉化為有根樹,考慮到每個結點所能達到的最遠距離一定是到它子節點的最大距離加上經過其父節點的最大距離,所以我們只需在dfs過程中維護這兩個值就可以在

最後O(n)得到答案。它到子節點的距離很好維護,關鍵是經過父節點的最長距離有兩種情況,第一種錄井是他的父節點經過他的父節點的父節點的距離,第二種是經過他的父節點再到他父節點的其他

子節點的路徑。第二種情況需要考慮他的父節點到子樹的最長距離是否就經過這個結點。為了判斷,我們需要再維護一個變數,就是當前結點到子樹的次長距離,這道題差不多就這樣了。

(害,感覺自己表達能力真的很成問題)

#include <stdio.h>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <map> 
#include <stack>
#include 
<sstream> #include <set> #pragma GCC optimize(2) //#define int long long #define mm(i,v) memset(i,v,sizeof i); #define mp(a, b) make_pair(a, b) #define pi acos(-1) #define fi first #define se second //你冷靜一點,確認思路再敲!!! using namespace std; typedef long long ll; const int N = 2e5 + 5, mod = 1e9 + 9
, INF = 0x3f3f3f3f; ll n, idx; ll e[N], ne[N], h[N], w[N], ls[N], da[N]; ll dp[5][N]; inline ll read(){ char c=getchar();ll x=0,f=1; while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();} while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();} return x*f; } void add(ll a, ll b, ll c) { e[idx] = b; w[idx] = c; ne[idx] = h[a]; h[a] = idx++; } void dfs(ll u, ll fa) { for (int i = h[u]; ~i; i = ne[i]) { ll v = e[i], z = w[i]; if (v == fa) continue; dfs(v, u); if (dp[0][v] + z > dp[0][u]) { dp[1][u] = dp[0][u]; dp[0][u] = dp[0][v] + z; ls[u] = v; } else if (dp[0][v] + z > dp[1][u]) dp[1][u] = dp[0][v] + z; } } void dfs2(ll u, ll fa) { for (int i = h[u]; ~i; i = ne[i]) { ll v = e[i], z = w[i]; if (v == fa) continue; if (ls[u] == v) dp[2][v] = max(dp[2][u], dp[1][u]) + z; else dp[2][v] = max(dp[2][u], dp[0][u]) + z; dfs2(v, u); } } void init() { idx = 0; mm(h, -1); mm(dp, 0); } int main() { while (cin >> n) { init(); for (int i = 2; i <= n; ++i) { int x, y; x = read(); y = read(); add(i, x, y); add(x, i, y); } dfs(1, -1); dfs2(1, -1); for(int i = 1; i <= n; i++) printf("%lld\n", max(dp[0][i], dp[2][i])); } } /* 5 1 3 1 4 1 5 1 7 7 10 11 12 12 */
View Code

2.CF 219D. Choosing Capital for Treeland

原題連結:https://codeforces.com/problemset/problem/219/D

題意:給出一個有向樹,你可以反轉邊的方向,找到滿足到所有點所需反轉邊數最少的點。輸出反轉的次數和所有滿足滿足的點。

很容易就可以想到對於這棵樹,我們把存在的邊的邊權設為0,然後增加其反邊權設為1。問題就轉換成了求點到所有別的點的耗費最小。

不妨假設1為改樹的根節點,進行一遍dfs得到每個結點到其子樹的耗費。用d維護。

然後進行第二次dfs,這次dfs我們從根節點開始往子節點更新,d表示每個點到其餘結點的距離。對於每個u和v如果u—>v權為1,那麼d[v] = d[u] - 1,否則d[v] = d[u] + 1

(感覺這題思路還是很巧妙的)

#include <stdio.h>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <map> 
#include <stack>
#include <sstream>
#include <set>
#pragma GCC optimize(2)

//#define int long long
#define mm(i,v) memset(i,v,sizeof i);
#define mp(a, b) make_pair(a, b)
#define pi acos(-1)
#define fi first
#define se second
//你冷靜一點,確認思路再敲!!! 

using namespace std;
typedef long long ll;
typedef pair<int, int > PII;
priority_queue< PII, vector<PII>, greater<PII> > que;
stringstream ssin; //  ssin << string   while ( ssin >> int)

const int N = 4e5 + 5, mod = 1e9 + 9, INF = 0x3f3f3f3f;
int n, idx, cnt, ans;
int e[N], h[N], ne[N], Size[N], d[N], w[N];
int dp[N], q[N];

void add(int a, int b, int c) {
    w[idx] = c;
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

inline int read(){
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
    return x*f;
}

void dfs1(int u, int fa) {
    for (int i = h[u]; ~i; i = ne[i]) {
        int v = e[i], z = w[i];
        if (v == fa) continue;
        dfs1(v, u);
        dp[u] += dp[v] + z;
    }
}

void dfs2(int u, int fa) {
    for (int i = h[u]; ~i; i = ne[i]) {
        int v = e[i], z = w[i];
        if (v == fa) continue;
        if (z == 1) dp[v] = dp[u] - 1;
        else dp[v] = dp[u] + 1;
        dfs2(v, u);
    }
    if (dp[u] < ans) {
        ans = dp[u];
        cnt = 0;
    }
    if (dp[u] == ans) {
        q[++cnt] = u;
    }
}
int main()
{
    mm(h, -1);
    cin >> n;
    for (int i = 1; i < n; ++i) {
        int a, b;
        a = read();
        b = read();
        add(a, b, 0);
        add(b, a, 1);
    }
    ans = INF;
    dfs1(1, -1);
    dfs2(1, -1);
    cout << ans << endl;
    sort(q + 1, q + 1 + cnt);
    for (int i = 1; i <= cnt; ++i) {
        cout << q[i] << " ";
    }
    puts("");
    // system("pause");
    return 0;
}
View Code