HZNUACM寒假集训Day10小结 树树形DP

  树形DP

  加分二叉树 洛谷P1040

  注意中序遍历的特点:当根节点编号k时,编号小于k的都在其左子树上,编号大于k的都在右子树

  转移方程 f[i,j]=max{f[i,k-1]*f[k+1,j]+d[k]} ,f[i,j]表示中序遍历i到j的二叉树最大加分  时间复杂度O(N3

  

#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<cmath>
const double PI = acos(-1.0);
typedef long long ll;
using namespace std;

const int maxn = 35;
int n, m;
int f[maxn][maxn], root[maxn][maxn], num[maxn];

void print(int l, int r) {         //先序遍历  根->左子树->右子树
    printf("%d ", root[l][r]);
    if (root[l][r] > l) print(l, root[l][r] - 1);
    if (root[l][r] < r) print(root[l][r] + 1, r);
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &num[i]);
        f[i][i] = num[i];
        root[i][i] = i;
        f[i][i - 1] = f[i + 1][i] = 1;
    }
    for (int i = n; i >= 1; i--) {
        for (int j = i + 1; j <= n; j++) {
            for (int k = i; k <= j; k++) {
                if (f[i][k - 1] * f[k + 1][j] + f[k][k] <= f[i][j]) continue;
                f[i][j] = f[i][k - 1] * f[k + 1][j] + f[k][k];
                root[i][j] = k;
            }
        }
    }
    printf("%d\n", f[1][n]);
    print(1, n);
    return 0;
}

    P2015 二叉苹果树

   

#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<cmath>
const double PI = acos(-1.0);
typedef long long ll;
using namespace std;

int n, cnt[110], dp[110][110];
int q;
struct Edge {
    int w;
    int e;
}t;
vector<Edge> e[220];
void dfs(int u, int p) {
    for (int i = 0; i < e[u].size(); i++) {
        int v = e[u][i].e;
        if (v == p) continue;
        dfs(v, u);
        cnt[u] += cnt[v] + 1;
        for (int j = min(cnt[u], q); j; j--) {
            for (int k = min(j - 1, cnt[v]); k >= 0;)
                dp[u][j] = max(dp[u][j], dp[u][j - k - 1] + dp[v][k] + e[u][i].w);
        }
    }
}
int main() {
    scanf("%d%d", &n, &q);
    for (int i = 1; i < n; i++) {
        int x, y, w;
        scanf("%d%d%d", &x, &y, &w);
        t.e = y;
        t.w = w;
        e[x].push_back(t);
        t.e = x;
        e[y].push_back(t);
    }
    dfs(1, 0);
    printf("%d", dp[1][q]);
    return 0;
}

    洛谷P1352 没有上司的舞会

   

#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<cmath>
const double PI = acos(-1.0);
typedef long long ll;
using namespace std;

const int maxn = 6005;
int w[maxn];
int v[maxn];
int f[maxn][2];
vector<int> son[maxn];

void dfs(int x) {                  
    f[x][0] = 0;                   //初始化  f[x][0]表示以x为根的子树,且x不参加舞会的最大快乐值
    f[x][1] = w[x];                //f[x][1]表示以x为根的子树,且x参加舞会的最大快乐值
    for (int i = 0; i < son[x].size(); i++) {
        int y = son[x][i];
        dfs(y);
        f[x][0] += max(f[y][0], f[y][1]);    //状态转移方程
        f[x][1] += f[y][0];                  
    }
}

int main() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &w[i]);
    for (int i = 1; i <= n - 1; i++) {
        int x, y;
        scanf("%d%d", &x, &y);    //注意父亲在后
        son[y].push_back(x);
        v[x]++;
    }
    int root;
    for (int i = 1; i <= n; i++) {
        if (!v[i]) {
            root = i;  break;
        }
    }
    dfs(root);
    printf("%d\n", max(f[root][0], f[root][1]));
    return 0;
}

   树上的常见操作

   

const int maxn = 100005;
vector<int> v[maxn];
int _size[maxn];    //结点大小
int mx_size[maxn];
int depth[maxn];
int Max[maxn];
int val[maxn];

void getsize(int x) {               //一棵N个点的无权树,问每个结点的大小
    _size[x] = 1;  
    for (int i = 0; i < v[x].size(); i++) {
        getsize(v[x][i]);
        _size[x] += _size[v[x][i]];
    }
}

void getdep(int x) {                //一棵N个点的无权树,问每个结点的深度
    for (int i = 0; i < v[x].size(); i++) {    
        depth[v[x][i]] = depth[x] + 1;
        getdep(v[x][i]);           //自顶向下
    }
}

void getmax(int x) {             //一棵N个点的点权树,问每个子树的点权和,点权最大值
    Max[x] = val[x];
    for (int i = 0; i < v[x].size(); i++) {
        getmax(v[x][i]);
        Max[x] = max(Max[x], Max[v[x][i]]);
    }
}

   求树的重心

   定义:找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心,删去重心后,生成的多棵树尽可能平衡

   

  C++代码

#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<cmath>
const double PI = acos(-1.0);
#define INF 0x3f3f3f3f
typedef long long ll;
using namespace std;

const int maxn = 105;
int n, m, ans;
int len, lenp;
int _size[maxn], id[maxn], p[maxn];
vector<int> e[maxn];
bool vis[maxn];

int dfs(int x) {
    if (!e[x].size()) return 1;
    int sum = 1;
    for (int i = 0; i < e[x].size(); i++) {
        if (!vis[e[x][i]]) {
            vis[e[x][i]] = true;
            sum += dfs(e[x][i]);
        }
    }
    return sum;
}

int main() {
    scanf("%d", &n);
    int x, y;
    for (int i = 1; i < n; i++) {
        scanf("%d%d", &x, &y);
        e[x].push_back(y);
    }
    for (int i = 1; i <= n; i++) {
        memset(vis, 0, sizeof vis);
        vis[i] = 1;
        _size[i] = dfs(i);
    }
    int Min = INF;
    for (int i = 1; i <= n; i++) {
        int Max = n - _size[i];
        for (int j = 0; j < e[i].size(); j++) Max = max(Max, _size[e[i][j]]);
        Min = min(Max, Min);
        p[i] = Max;
    }
    for (int i = 1; i <= n; i++) {
        if (p[i] == Min) {
            lenp++;
            id[lenp] = i;
        }
    }
    printf("%d\n", lenp);             //重心个数
    for (int i = 1; i <= lenp; i++) printf("%d\n", id[i]);
    return 0;
}
原文地址:https://www.cnblogs.com/hznumqf/p/12272614.html