樹形DP問題

Placing Lampposts

題目傳送:UVA - 10859 - Placing Lampposts

巧妙的思想:

對於有兩個需要優化的量v1和v2的時候,要求首先滿足v1最小,在v1相同的情況下v2最小,則這裏可以把二者組合成一個量M*v1+v2,其中M是一個比”v2的最大理論值和v2的最小理論值之差”還要大的數。

AC代碼:

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <string>
#include <vector>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <utility>
#include <iostream>
#include <algorithm>
#include <functional>
#define LL long long
#define INF 0x7fffffff
using namespace std;

vector<int> G[1005];
int vis[1005][2], d[1005][2];
int n, m;

int dp(int i, int j, int f) {
    if(vis[i][j]) return d[i][j];
    vis[i][j] = 1;
    int& ans = d[i][j];

    ans = 2000;
    for(int k = 0; k < G[i].size(); k ++) {
        if(G[i][k] != f) {
            ans += dp(G[i][k], 1, i);
        }
    }
    if(f >= 0 && !j) ans ++;

    if(j || f < 0) {
        int sum = 0;
        for(int k = 0; k < G[i].size(); k ++) {
            if(G[i][k] != f) {
                sum += dp(G[i][k], 0, i);
            }
        }
        if(f >= 0) sum ++;
        ans = min(ans, sum);
    }
    return ans;
}

int main() {
    int T;
    scanf("%d", &T);
    while(T --) {
        scanf("%d %d", &n, &m);
        for(int i = 0; i < n; i ++) G[i].clear();
        int a, b;
        for(int i = 0;  i < m; i ++) {
            scanf("%d %d", &a, &b);
            G[a].push_back(b);
            G[b].push_back(a);
        }
        memset(d, 0, sizeof(d));
        memset(vis, 0, sizeof(vis));
        int ans = 0;
        for(int i = 0; i < n; i ++) {
            if(!vis[i][0]) {
                ans += dp(i, 0, -1);
            }
        }
        printf("%d %d %d\n", ans / 2000, m - ans % 2000, ans % 2000);
    }
    return 0;
}



刷油漆

題目傳送:hihoCoder - 1055 - 刷油漆

昨天第一次做樹形DP的題,感覺很神奇!思想甚是巧妙!

今天這個樹形DP是類似揹包的一個題,因爲遞推方程差不多

不過要注意一點,枚舉每一個子樹的那一層循環只能放最外面

因爲:
如果把枚舉每一個子樹放到最內層,枚舉”揹包容量”放到最外面一層,則這樣會產生最優解沒有及時更新到最外面那一層的情況,導致出錯。比如n=INF,m=9,根結點取1(因爲必須取根節點),他有很多子樹,而取其中兩個子樹3個結點和5個節點爲最優解。當取第二個子樹5個結點時,因爲dp[1][4](即第一個子樹的3個結點對應的應該已經更新的位置)還沒更新(因爲是倒序枚舉m),此時得不到最優解。

AC代碼:

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <string>
#include <vector>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <utility>
#include <iostream>
#include <algorithm>
#include <functional>
#define LL long long
#define INF 0x7fffffff
using namespace std;

const int maxn = 105;
int dp[105][105];//dp[i][j]表示在以i爲根的一棵樹中,選出包含根節點i的j個連通的結點,能夠獲得的最高的評分,然後我們的答案就是dp[1][m].

int n, m;
vector<int> G[maxn];

void dfs(int u, int fa) {
    int d = G[u].size();
    for(int i = 0; i < d; i ++) {
        if(G[u][i] != fa) dfs(G[u][i], u);
    }
    for(int k = 0; k < d; k ++) {//枚舉每一個子節點,這裏必須把枚舉每一個子節點放在外面,因爲可能在遍歷完這個子樹之後,再遍歷另一個子樹可能會得到最優解,而每次只枚舉每個子樹的一部分得不到最優解
        int v = G[u][k];
        if(v != fa)
        for(int j = m; j >= 1; j --) {//倒序枚舉刷油漆的數量,類似揹包
            for(int i = 1; i < j; i ++) {
                dp[u][j] = max(dp[u][j], dp[u][j - i] + dp[v][i]);
            }
        }
    }
}

int main() {
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; i ++) {
        scanf("%d", &dp[i][1]);
    }
    int u, v;
    for(int i = 1; i < n; i ++) {
        scanf("%d %d", &u, &v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(1, -1);
    printf("%d\n", dp[1][m]);
    return 0;
}




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章