• 树形DP入门


    给定一颗有N个节点的树(一般是无根树,就有N-1条无向边),可以任选一个节点作为根节点

    一般以节点从深到浅(子树从小到大)的顺序作为dp阶段顺序

    dp的状态表示中,第一维通常是节点编号(节点编号代表了以该节点为根的子树)

    对于每个节点x,先递归在它的每个子节点上进行dp,回溯时,从子节点向x进行状态转移

    A - Anniversary part

    N个员工,编号为1~N

    他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直属上司。现在有个宴会,宴会每邀请来一个员工 i 都会增加一定的快乐指数 Ri,但如果某个员工的直属上司来了,那么这个员工就不会来。计算邀请哪些员工可以使快乐指数最大,输出最大的快乐指数

    dp [x] [0] 表示在 x 为根的子树中邀请部分员工,并且 x 不参加时,快乐指数总和的最大值,此时 x 子节点(直接下属)可以参加也可以不参加 (s表示x子节点)

    dp [x] [1] 表示在 x 为根的子树中邀请部分员工,并且 x 参加时,快乐指数总和的最大值,此时 x 子节点(直接下属)都不能参加,H[x] 表示当前节点(x)的快乐指数 (s表示x子节点)

    这个题给的是有根树,就可以从根节点开始,dp目标就是 max(F[root,0] , F[root,1]) 时间复杂度ON

    #include <cstdio>
    #include <iostream>
    #include <cstring>
    #include <algorithm>
    #include <vector>
    using namespace std;
    
    vector<int> son[10010];
    int dp[10010][2];//0不参加,1参加
    int v[10010];//记录有没有父节点
    int h[10010];//记录快乐指数
    int n;
    
    void DFS(int x){
        dp[x][0] = 0;
        dp[x][1] = h[x];
        for (int i = 0; i < son[x].size(); i++)
        {
            int y = son[x][i];
            DP(y);
            dp[x][0] += max(dp[y][0],dp[y][1]);
            dp[x][1] += dp[y][0];
        }
    }
    
    int main(){
        cin>>n;
        for (int i = 1; i <=n ; i++)
            scanf("%d",&h[i]);
        for (int i = 1; i <n ; i++)
        {
            int x,y;
            cin>>x>>y;
            v[x] = 1;//x有爸爸
            son[y].push_back(x);//x是y的子节点
        }
        int root;
        for (int i = 1; i <= n; i++)
            if(!v[i]){ //i没有爸爸
                root = i;
                break;
            }
        DFS(root);
        cout << max(dp[root][0],dp[root][1]) << endl;
        return 0;
    }
    

    B - Strategic game

    有n个点,在某些点上放置哨兵,每个哨兵可以监控和它有边相连的点,问监视所有的点需要的最少的哨兵数

    也就是说一颗n个结点的树,要求选出其中的一些顶点,使得对于树中的每条边(u, v),u和v至少有一个被选中,要求给出选中顶点数最少的方案

    dp [x] [0] 表示在不选择节点 x 的情况下,以 x 为根节点的子树,最少需要选择的节点数

    ​ 当i为叶子节点时

    ​ 当i不为叶子节点时 (s表示x子节点)

    dp [x] [1] 表示在选择节点 x 的情况下,以 x 为根节点的子树,最少需要选择的节点数
    ​ 当i为叶子节点时

    ​ 当i不为叶子节点时 (s表示x子节点)

    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <cmath>
    #define maxn 1508
    using namespace std;
    
    int dp[maxn][2];
    int soncnt[maxn];
    int parent[maxn];
    int n;
    
    void DFS(int x) {
        int i, d1=0, d0=0;
        if (soncnt[x] == 0) {
            dp[x][0] = 0;
            dp[x][1] = 1;
            return;
        }
        for (i=0; i < n; i++) {
            if (parent[i] == x) {
                DFS(i);
                d1 += min(dp[i][0], dp[i][1]);
                d0 += dp[i][1];
            }
        }
        dp[x][1] = d1 + 1;
        dp[x][0] = d0;
    }
    
    int main() {
        int dad, son, m;
        while (cin >> n) {
            memset(soncnt, 0, sizeof(soncnt));
            memset(parent, -1, sizeof(parent));
            int root = -1;
            for (int i = 0; i < n; i++) {
                scanf("%d:(%d)", &dad, &m);
                soncnt[dad] = m;
                if (root == -1) {
                    root = dad;
                }
                while (m--) {
                    scanf("%d", &son);
                    parent[son] = dad;
                }
            }
            DFS(root);
            cout << min(dp[root][0], dp[root][1]) << endl;
        }
        return 0;
    }
    

    C - Tree Cutting

    给一颗n个结点的树,节点编号为1~n

    问:删除哪些结点后,剩余各个子树的大小均小于原总结点数的一半

    拆除一个节点后,剩余部分为其若干儿子的子树以及该节点上层所连其余部分(n-size[i]),只要这些连接块大小都不超过n/2,该节点就满足条件。因而我们可以先求出每个节点所管辖的那棵树的大小,自下而上地为每个节点求出其子树规模(该点规模=其儿子的规模和+1)。

    在建树的时候可以直接用连接表(vector)储存无向边,这时由于无法区分与每个点相连的是其父节点还是子节点,会引发问题:在dfs的时候把父节点误认作儿子节点,解决方法就是在递归的时候传入父节点编号,然后在递归,计算规模,比较大小的时候避开它就可以了

    #include<iostream>
    #include<cstring>
    #include<vector>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
     
    int n;
    int root;
    vector<int>G[10000+400];
    vector<int>ans;
    int sz[10000+400];
     
    void dfs(int par,int u){
        sz[u]=1;
        for(int i=0;i<(int)G[u].size();i++){
            if(G[u][i]!=par)
                dfs(u,G[u][i]);
        }
        int piece=0;
        for(int i=0;i<(int)G[u].size();i++)
            if(par!=G[u][i]){
                sz[u]+=sz[G[u][i]];
                piece=max(piece,sz[G[u][i]]);
            }
        piece=max(piece,n-sz[u]);
        if(piece<=n/2)
            ans.push_back(u);
    }
     
    int main(){
        while(cin>>n){
            memset(sz,0,sizeof(sz));
            int x,y;
            for(int i=0;i<n-1;i++){
                scanf("%d%d",&x,&y);
                G[x].push_back(y);
                G[y].push_back(x);
            }
            dfs(0,1);
            sort(ans.begin(),ans.end());
            for(int i=0;i<(int)ans.size();i++)
                cout<<ans[i]<<endl;
        }
        return 0;
    }
    

    LCA 最近公共祖先

    倍增(基于二分的方法)

    假如树上的两个点,处于同一深度时,对于往上跳mid步,显然有单调性:

    如果它们往上跳mid步是同一个点,则这个点是它们的共同祖先,但不一定是最近公共祖先

    那么如果我们可以快速得到每个点往上跳若干步是谁,显然我们可以利用二分来求LCA

    这个可以用倍增来做。

    倍增的思想其实非常简单,就是利用二进制的思想。一个显然的结论有:

    节点u往上跳2^(k+1)的布的祖先 = (节点u往上跳2^k布的祖先)往上跳2^k布的祖先

    用代码表示就是:

    fa[u][k + 1] = fa[ fa[u][k] ][k];
    

    这个的预处理也非常简单,我们只需要初始化每个fa[u][0]就可以了就可以了,因为接下来的部分都可以根据上面的公式递推。

    然后预处理好这个之后,我们就可以根据这个进行二分了,代码如下:

    int fa[maxn][20], dep[maxn];
    
    void dfs(int u, int f, int d)
    {
        dep[u] = d;
        fa[u][0] = f;
        for(int i = 1; i < 20; ++i)
        {
            fa[u][i] = fa[fa[u][i -  1]][i] - 1;
        }
        for(int i = head[u]; ~i; i = nxt[i])
        {
            int t = to[i];
            if(t != f)
            {
                dfs(t, u, d + 1);
            }
        }
    }
    
    int lca(int u, int v)
    {
        if(dep[u] < dep[v]) swap(u, v);
        int k = dep[u] - dep[v];
        for(int i = 0; i < k; ++i)
        {
            if((1 << i) & k) u = fa[u][i];
        }
        if(u == v) return u;
        for(int i = 19; i >= 0; --i)
        {
            if(fa[u][i] != fa[v][i])
            {
                u = fa[u][i];
                v = fa[v][i];
            }
        }
        return fa[u][0];
    }
    
  • 相关阅读:
    POJ 1637:Sightseeing tour
    bzoj 3997: [TJOI2015]组合数学
    [CEOI2008]order
    【网络流24题】星际转移问题
    Codeforces Round #460 D. Karen and Cards
    bzoj 3142: [Hnoi2013]数列
    codeforces586B
    codeforces631B
    codeforces548B
    codeforces515B
  • 原文地址:https://www.cnblogs.com/zhxmdefj/p/11148993.html
Copyright © 2020-2023  润新知