zoukankan      html  css  js  c++  java
  • 专题3

    树形dp,顾名思义,给出一棵树并对其赋予权值(或边权或点权),在树上进行动态规划。

    其基本思路是从根节点开始进行记忆化搜索,或是从叶子节点开始自下而上正向推进。

    NC22598

    题目中要求所有度为1的点都不能到达关键点(S),那么问题可以转述成“对于一棵以(S)点位根节点的树,所有的叶子节点都不能到达根(S),这边给出一个棵树:

    在这张图中,假设关键点为(1),那么我需要保证(2,4,5)这三个点无法到达(1),对于(4,5)两个点而言,我可以选择删去(1->3)这条边,也可以分别删去(3->4)和(3->5)两条边。

    由此可以得出状态转移方程(f[i]=max{f[j]}),如果为叶子节点,返回该节点与其父节点的边权。

    有关存图与搜索的方式可以分为以下两种。

    (1) 如果父子关系明确,可以采用有向图的方式存邻接表。

    (2) 对于任意树,都可以采用无向图的方式存邻接表,在搜索时记录该节点的父节点防止走回头路。

    #include <bits/stdc++.h>
    #define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
    #define ll long long
    #define pb push_back
    using namespace std;
    const int maxn = 1e5 + 10;
    const ll INF = 1e18;
    int n, m, s;
    vector<int> v[maxn];
    map<pair<int, int>, ll> mp;
    ll f[maxn];
    ll dfs(int x, int fa)
    {
        if (v[x].size() == 1 && x != s)
            return mp[make_pair(x, fa)];
        for (int i = 0; i < v[x].size(); i++)
        {
            if (v[x][i] == fa)
                continue;
            f[x] += dfs(v[x][i], x);
        }
        if (fa == 0)
            return f[x];
        return f[x] = min(f[x], mp[make_pair(x, fa)]);
    }
    int main()
    {
        fast;
        cin >> n >> m >> s;
        int uu, vv, w;
        for (int i = 1; i <= m; i++)
        {
            cin >> uu >> vv >> w;
            v[uu].pb(vv);
            v[vv].pb(uu);
            mp[make_pair(uu, vv)] = w;
            mp[make_pair(vv, uu)] = w;
        }
        for (int i = 1; i <= n; i++)
        {
            f[i] = 0;
        }
        cout << dfs(s, 0) << '
    ';
    }

    NC202475

    题目相当的直接,找出权值最大的子链。

    最关键的地方在于子链的定义。

    仍然是这一棵树,(1->3->5)固然是一条子链,然而如同(4->3->5),(2->1->3->5)这类也算作子链。在状态转移时,我们转移以该节点为根的情况下子链权值的最大值,对于每个节点,找到权值最大的两条子链并将权值相加。

    #include <bits/stdc++.h>
    #define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
    #define ll long long
    #define pb push_back
    using namespace std;
    const int maxn = 1e5 + 10;
    int n;
    ll a[maxn];
    ll dp[maxn] = {0};
    ll maxx = -1e9;
    vector<int> v[maxn];
    void dfs(int x, int pre)
    {
        dp[x] = a[x];
        for (int i = 0; i < v[x].size(); i++)
        {
            if (v[x][i] == pre)
                continue;
            dfs(v[x][i], x);
            maxx = max(maxx, dp[x] + dp[v[x][i]]);
            dp[x] = max(dp[x], dp[v[x][i]] + a[x]);
        }
        maxx = max(maxx, dp[x]);
    }
    int main()
    {
        cin >> n;
        for (int i = 1; i <= n; i++)
        {
            cin >> a[i];
        }
        for (int i = 1; i < n; i++)
        {
            int p, q;
            cin >> p >> q;
            v[p].pb(q);
            v[q].pb(p);
        }
        dfs(1, 0);
        cout << maxx << '
    ';
    }

    NC15033

    再把这个图复制下来:

    对于节点(3),它的子树包括({4}),({5}),({1,2}),我们可以从任意点作为根节点进行搜索,在这棵根确定的树中,对于任意一点(x),不仅需要考虑子树,还需要向上考虑剩余的节点数量(因为当(x)作为根节点是,这些剩下的点也将成为一棵子树)。

    在状态转移时,用(f[x])表示以该节点为根的子树的节点数,那么( -f[x])就是剩余部分的节点数,找到其中的最小值即可(节点数越小,越平衡)。

    #include <bits/stdc++.h>
    #define ll long long
    #define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
    #define pb push_back
    using namespace std;
    const int maxn = 1010;
    vector<int> v[maxn];
    int dp[maxn];
    int ansx = 1e9, ansnum = 1e9;
    int n;
    void dfs(int x, int pre)
    {
        dp[x] = 1;
        int m = 0;
        for (int i = 0; i < v[x].size(); i++)
        {
            if (v[x][i] == pre)
                continue;
            dfs(v[x][i], x);
            dp[x] += dp[v[x][i]];
            m = max(m, dp[v[x][i]]);
        }
        m = max(m, n - dp[x]);
        if (m < ansnum)
        {
            ansnum = m;
            ansx = x;
        }
        else if (m == ansnum)
        {
            if (ansx > x)
                ansx = x;
        }
    }
    int main()
    {
        fast;
        cin >> n;
        int p, q;
        for (int i = 1; i < n; i++)
        {
            cin >> p >> q;
            v[p].pb(q);
            v[q].pb(p);
        }
        dfs(1, 0);
        cout << ansx << ' ' << ansnum << '
    ';
    }

    NC51178(最大独立集)

    状态转移思路差别不大,只是对于一个节点,需要和他孩子的孩子建立关系。因为上面的所有题目都是父子关系,所以不需要进行记忆化搜索,但这里对于每个点,都有可能被搜索到两次,所以要用到记忆化搜索。

    #include <bits/stdc++.h>
    #define ll long long
    #define pb push_back
    #define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
    using namespace std;
    const int maxn = 6010;
    ll h[maxn];
    int vis[maxn];
    ll f[maxn];
    vector<int> mp[maxn];
    ll dfs(int x)
    {
        if (f[x])
            return f[x];
        ll tmp1 = 0;
        ll tmp2 = 0;
        for (int i = 0; i < mp[x].size(); i++)
        {
            int nex = mp[x][i];
            tmp2 += dfs(mp[x][i]);
            for (int j = 0; j < mp[nex].size(); j++)
            {
                tmp1 += dfs(mp[nex][j]);
            }
        }
        return f[x] = max(tmp1 + h[x], tmp2);
    }
    int main()
    {
        fast;
        int n;
        cin >> n;
        for (int i = 1; i <= n; i++)
        {
            cin >> h[i];
        }
        int u, v;
        for (int i = 1; i < n; i++)
        {
            cin >> u >> v;
            vis[u]++;
            mp[v].pb(u);
        }
        cin >> u >> v;
        for (int i = 1; i <= n; i++)
        {
            f[i] = 0;
        }
        for (int i = 1; i <= n; i++)
        {
            if (!vis[i])
                dfs(i), cout << f[i] << '
    ';
        }
    }

    NC24953(最小支配集)

    最小支配集含义为当树中一个点被选中时,其直接相连的点都视为被选中,问这棵树最少需要多少个点可以将所有点全部选中。

    一个点被选中有三种情况:

    (1) 当前点被直接选中

    (2) 其子节点被选中

    (3) 其父节点被选中

    那么我们就用(f[i][j])表示第(i)个点的三种情况下最小的结果。(三种情况分别对应(j)取(0,1,2))

    对于(1),直接找到子节点三种情况的最小值相加即可。

    对于(3),只要考虑子节点(1)(2)两种情况的最小值相加即可。

    情况(2)相对复杂,该情况下,其子节点至少有一个被选中,但最优情况下可能不会被选中,因此需要考虑:

    1. 如果某个子节点选中时情况最优,直接选择,且不需要后续操作

    2. 若均为选中子节点的子节点情况最优,然而现在要求必须要选上一个子节点,所以需要补上差值(min(f[u][0]-f[u][1]))。

    最终的结果为(min(f[1][0],f[1][1])),没有(f[1][2])。(骂自己)

    #include <bits/stdc++.h>
    #define ll long long
    #define pb push_back
    #define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
    using namespace std;
    const int maxn = 10010;
    const int INF = 0x3f3f3f3f;
    vector<int> edge[maxn];
    int f[maxn][3];
    int n;
    void dfs(int u, int fa)
    {
        int flag = 1;
        int tmp = INF;
        f[u][0] = 1;
        f[u][1] = 0;
        f[u][2] = 0;
        for (int v : edge[u])
        {
            if (v == fa)
                continue;
            dfs(v, u);
            f[u][0] += min({f[v][0], f[v][1], f[v][2]});
            f[u][2] += min(f[v][0], f[v][1]);
            if (f[v][0] <= f[v][1])
            {
                flag = 0;
                f[u][1] += f[v][0];
            }
            else
            {
                f[u][1] += f[v][1];
                tmp = min(tmp, f[v][0] - f[v][1]);
            }
        }
        if (flag)
            f[u][1] += tmp;
    }
    int main()
    {
        cin >> n;
        int u, v;
        for (int i = 1; i < n; i++)
        {
            cin >> u >> v;
            edge[u].pb(v);
            edge[v].pb(u);
        }
        dfs(1, 0);
        cout << min(f[1][0], f[1][1]) << '
    ';
    }
  • 相关阅读:
    git相关指令
    深刻理解回调函数
    将本地项目托管到GitHub、码云、GitLab的步骤
    各种安装依赖、插件、创建项目的指令
    IOS MBProgressHUD的使用
    2012武汉华为机试题
    GTK+2 多线程模型
    bindtextdomain()/textdomain() 设置文本域目录及文本域文件
    在Linux下开发多语言软件: Hello GetText!
    转:getaddrinfo函数详解
  • 原文地址:https://www.cnblogs.com/endlesskkk/p/15362407.html
Copyright © 2011-2022 走看看