树形dp

树形dp,意思就是在树上的dp, 看了看紫书,讲了三个大点把,一个是树的最大独立集,另外一个是树的重心,最后一个是树的最长路径。给的三个例题,下面就从例题说起

第一个:工人的请愿书 uva 12186

这个题目给定一个公司的树状结构,每个员工都有唯一的一个直属上司,老板编号为0,员工1-n,只有下一级的工人请愿书不小于T%时,这个中级员工,才会签字传递给它的直属上司,问老板收到请愿书至少需要多少各个工人签字

用dp(u)表示u给上级发信至少需要多少工人,那么可以假设u有k个节点,所以需要c = (T*k - 1) / 100 + 1个直接下属,把所有的子节点的dp值从小到大排序,前c个加起来就是答案。

树形dp就是从根节点一层一层往下找,找到最优的子结构,树形的最优子结构就是子树,我是用的记忆化搜索。代码如下:

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 10;
vector<int> sons[maxn];
int n, t;
int dp(int u)
{
    if (sons[u].empty())
        return 1;
    int k = sons[u].size();
    vector<int> d;
    for (int i = 0; i < k; i++)
        d.push_back(dp(sons[u][i]));
    sort(d.begin(), d.end());
    int c = (k * t - 1) / 100 + 1;
    int ans = 0;
    for (int i = 0; i < c; i++)
        ans += d[i];
    return ans;
}
void init()
{
    for (int i = 0; i <= n; i++)
        sons[i].clear();
}
int main()
{
    while (~scanf("%d%d", &n, &t) && n + t)
    {
        init();
        int t;
        for (int i = 1; i <= n; i++)
        {
            scanf("%d", &t);
            sons[t].push_back(i);
        }
        printf("%d\n", dp(0));
    }
    return 0;
}

第二个:Hali-Bula的晚会,poj3342, uva1220

这个几乎是求树的最大独立集的模板题,就是多了一个要求,判断是否唯一

考虑一个点,只有两种情况,选它,不选它

所以用 d[u][0]表示以u为根的子树中,不选u点能得到的最大人数 f[u][0]表示方案唯一性,如果f[u][0] = 1说明唯一,0说明不唯一

d[u][1]表示以u为根,选u点能得到的最大值。

状态转移方程就是d[u][1]  = sum{d[v][0]}| v是u的子节点

d[u][0] = sum{max(d[v][0], d[v][1])},所以代码如下:

#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <cstring>
#include <map>
using namespace std;
const int maxn = 240;
vector<int> sons[maxn];
map<string, int> mp;
int f[maxn][2], d[maxn][2];
int n, cnt;
void init()
{
    memset(d, -1, sizeof(d));
    for (int i = 0; i <= n; i++)
        sons[i].clear();
    mp.clear();
}
int dp(int u, int flag)
{
    f[u][flag] = 1;
    if (sons[u].empty() && flag)
        return 1;
    if (sons[u].empty() && !flag)
        return 0;
    int k = sons[u].size();
    int sum = 0;
    if (flag == 1)
    {
        sum++;
        for (int i = 0; i < k; i++)
        {
            int v = sons[u][i];
            if (d[v][0] == -1)
                d[v][0] = dp(v, 0);
            sum += d[v][0];
            f[u][1] &= f[v][0];
        }
    }
    else
    {
        for (int i = 0; i < k; i++)
        {
            int v = sons[u][i];
            if (d[v][1] == -1)
                d[v][1] = dp(v, 1);
            if (d[v][0] == -1)
                d[v][0] = dp(v, 0);
            if (d[v][0] == d[v][1])
                f[u][flag] = 0;
            if (d[v][0] > d[v][1])
                f[u][0] &= f[v][0];
            else
                f[u][0] &= f[v][1];
            sum += max(d[v][1], d[v][0]);
        }
    }
    return sum;
}

int main()
{
    while (~scanf("%d", &n) && n)
    {
        init();
        string s, tmp;
        cin >> s;
        mp[s] = 0;
        cnt = 1;
        for (int i = 1; i < n; i++)
        {
            cin >> s >> tmp;
            if (mp.count(s) == 0)
                mp[s] = cnt++;
            if (mp.count(tmp) == 0)
                mp[tmp] = cnt++;
            sons[mp[tmp]].push_back(mp[s]);
        }
        int t1 = dp(0, 0); int t2 = dp(0, 1);
        bool flag = true;
        //cout << f[0][0] << endl;
        //cout << f[0][1] << endl;
        //cout << t2 << endl;
        if (t1 == t2)
            flag = false;
        if (t1 < t2 && f[0][1] == 0)
            flag = false;
        if (t1 > t2 && f[0][0] == 0)
            flag = false;
        printf("%d %s\n", max(t1, t2), flag ? "Yes" : "No");
    }
    return 0;
}

第三个:完美的服务 poj3398, uva 1218

这个和第二题差不多,就是状态多了一个,因为有个条件是每台计算机连接的服务器恰好是一个,所以多了一个状态,我是用记忆化搜索来写的,写完之后一直wrong answer,后来对比了网上的代码发现把inf设的太大了,估计是后来加着加着越界了,所以wrong了,后来改了就好了,不过记忆化写起来比递推代码长多了。。。

记忆化代码:

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
const int maxn = 10010;
const int inf = (1e6);
vector<int> sons[maxn];
int n;
int d[maxn][3];
void init()
{
    for (int i = 0; i <= n; i++)
        d[i][0] = d[i][1] = d[i][2] = inf;
    for (int i = 0; i <= n; i++)
        sons[i].clear();
}
int dp(int u, int pre, int f)
{
    if (sons[u].size() == 1 && sons[u][0] == pre)
        if (f == 0)
            return 1;
        else if (f == 1)
            return 0;
        else
            return inf;
    int k = sons[u].size();
    int sum = 0;
    if (f == 0)//u is server
    {
        sum++;
        for (int i = 0; i < k; i++)
        {
            int v = sons[u][i];
            if (pre == v)
                continue;
            if (d[v][0] == inf)
                d[v][0] = dp(v, u, 0);
            if (d[v][1] == inf)
                d[v][1] = dp(v, u, 1);
            sum += min(d[v][0], d[v][1]);
        }
    }
    else if (f == 1)//u is not server, his father is server
    {
        for (int i = 0; i < k; i++)
        {
            int v = sons[u][i];
            if (v == pre)
                continue;
            if (d[v][2] == inf)
                d[v][2] = dp(v, u, 2);
            sum += d[v][2];
        }
    }
    else//u is not server and his father is not server;
    {
        int ans = inf;
        for (int i = 0; i < k; i++)
        {
            int v = sons[u][i];
            if (pre == v)
                continue;
            if (d[u][1] == inf)
                d[u][1] = dp(u, pre, 1);
            if (d[v][2] == inf)
                d[v][2] = dp(v, u, 2);
            if (d[v][0] == inf)
                d[v][0] = dp(v, u, 0);
            ans = min(ans, d[u][1] - d[v][2] + d[v][0]);
        }
        sum += ans;
    }
    return sum;
}
int main()
{
    int a, b;
    while (~scanf("%d", &n) && n != -1)
    {
        if (n == 0)
            continue;
        init();
        for (int i = 1; i < n; i++)
        {
            scanf("%d %d", &a, &b);
            sons[a].push_back(b);
            sons[b].push_back(a);
        }
        int t1 = dp(1, 0, 0); int t2 = dp(1, 0, 2);
        //cout << t1 << endl;
        //cout << t2 << endl;
        printf("%d\n", min(t1, t2));
    }
    return 0;
}

递推代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
const int maxn = 11000;
const int inf = 1e6;
vector<int> sons[maxn];
int n;
int d[maxn][3];
void init()
{
    for (int i = 0; i <= n + 10; i++)
    {
        d[i][0] = 1; d[i][1] = 0; d[i][2] = inf;
    }
    for (int i = 0; i <= n; i++)
        sons[i].clear();
}
void dp(int u, int pre)
{
    int k = sons[u].size();
    for (int i = 0; i < k; i++)
    {
        int v = sons[u][i];
        if (pre == v)
            continue;
        dp(v, u);
        d[u][0] += min(d[v][0], d[v][1]);
        d[u][1] += d[v][2];
        d[u][2] = min(d[u][2], d[v][0] - d[v][2]);
    }
    d[u][2] += d[u][1];
}
int main()
{
    int a, b;
    while (~scanf("%d", &n) && n != -1)
    {
        if (n == 0)
            continue;
        init();
        for (int i = 1; i < n; i++)
        {
            scanf("%d %d", &a, &b);
            sons[a].push_back(b);
            sons[b].push_back(a);
        }
        dp(1, 0);
        printf("%d\n", min(d[1][0], d[1][2]));
    }
    return 0;
}

时间: 2024-10-17 09:53:59

树形dp的相关文章

HDU-2196 Computer (树形DP)

最近在看树形DP,这题应该是树形DP的经典题了,写完以后还是有点感觉的.之后看了discuss可以用树分治来做,以后再试一试. 题目大意 找到带权树上离每个点的最远点.︿( ̄︶ ̄)︿ 题解: 对于每一个点的最远点,就是以这个点为根到所有叶子节点的最长距离.但是如果确定根的话,除了根节点外,只能找到每个节点(度数-1)个子树的最大值,剩下一个子树是该节点当前的父亲节点. 所以当前节点的最远点在当前节点子树的所有叶子节点以及父亲节点的最远点上(当父亲节点的最远点不在当前节点的子树上时), 如果父亲节

UVA-01220 Party at Hali-Bula (树形DP+map)

题目链接:https://vjudge.net/problem/UVA-1220 思路: 树形DP模板题,求最大人数很简单,难点在于如何判断最大人数的名单是否有不同的情况: 解决方法是用一个数组f[manx][2]记录该节点是否出场的情况,为真时代表有多种情况; 具体讨论: 当父节点的值加上某个子节点的值时,他的f的情况也和该子节点一样: 当某个节点dp(i, 0) == dp(i, 1), 则该节点以及它的父节点也一定有多种情况(父节点必定取其中之一). Code: 1 #include<bi

HDU 1520 树形dp裸题

1.HDU 1520  Anniversary party 2.总结:第一道树形dp,有点纠结 题意:公司聚会,员工与直接上司不能同时来,求最大权值和 #include<iostream> #include<cstring> #include<cmath> #include<queue> #include<algorithm> #include<cstdio> #define max(a,b) a>b?a:b using nam

HDU2196 Computer(树形DP)

和LightOJ1257一样,之前我用了树分治写了.其实原来这题是道经典的树形DP,感觉这个DP不简单.. dp[0][u]表示以u为根的子树中的结点与u的最远距离 dp[1][u]表示以u为根的子树中的结点与u的次远距离 这两个可以一遍dfs通过儿子结点转移得到.显然dp[0][u]就是u的一个可能的答案,即u往下走的最远距离,还缺一部分就是u往上走的最远距离: dp[2][u]表示u往上走的最远距离 对于这个的转移,分两种情况,是这样的: dp[2][v] = max( dp[0][u]+w

hdu5593--ZYB&#39;s Tree(树形dp)

问题描述 ZYB有一颗N个节点的树,现在他希望你对于每一个点,求出离每个点距离不超过KK的点的个数. 两个点(x,y)在树上的距离定义为两个点树上最短路径经过的边数, 为了节约读入和输出的时间,我们采用如下方式进行读入输出: 读入:读入两个数A,B,令fai??为节点i的父亲,fa?1??=0;fa?i??=(A∗i+B)%(i−1)+1,i∈[2,N] . 输出:输出时只需输出N个点的答案的xor和即可. 输入描述 第一行一个整数TT表示数据组数. 接下来每组数据: 一行四个正整数N,K,A,

CF 219D Choosing Capital for Treeland 树形DP 好题

一个国家,有n座城市,编号为1~n,有n-1条有向边 如果不考虑边的有向性,这n个城市刚好构成一棵树 现在国王要在这n个城市中选择一个作为首都 要求:从首都可以到达这个国家的任何一个城市(边是有向的) 所以一个城市作为首都,可能会有若干边需要改变方向 现在问,选择哪些城市作为首都,需要改变方向的边最少. 输出最少需要改变方向的边数 输出可以作为首都的编号 树形DP 先假定城市1作为首都 令tree(i)表示以i为根的子树 dp[i]表示在tree(i)中,若以i为首都的话,需要改变的边数 第一次

HDU 1011 Starship Troopers(树形dp+背包)

Starship Troopers Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 13109    Accepted Submission(s): 3562 Problem Description You, the leader of Starship Troopers, are sent to destroy a base of

Codeforces 462D Appleman and Tree 树形dp

题目链接:点击打开链接 题意: 给定n个点的树, 0为根,下面n-1行表示每个点的父节点 最后一行n个数 表示每个点的颜色,0为白色,1为黑色. 把树分成若干个联通块使得每个联通块有且仅有一个黑点,问有多少种分法(结果mod1e9+7) 思路: 树形dp,每个点有2个状态,已经归属于某个黑点和未归属于某个黑点. #include <cstdio> #include <vector> #include <iostream> using namespace std; #de

poj 3342(树形dp)

题意:在一个公司中要举办一个聚会,每一个员工有一个奉献值.为了和谐规定直接上下级不能一起出席.让你找出奉献值之和最大为多少. 思路:dp[v][1]表示当前结点选,能获得的最大奉献值,dp[v][0]表示当前节点不选能获得的最大奉献值.状态转移: dp[v][0] = max(dp[v][0], ∑max(dp[x][1], dp[x][0]))x为直接儿子 dp[v][1] = max(dp[v][1], ∑dp[x][0] + vex[v]) 最后答案是max(dp[root][0], dp

CodeForces 219D.Choosing Capital for Treeland (树形dp)

题目链接: http://codeforces.com/contest/219/problem/D 题意: 给一个n节点的有向无环图,要找一个这样的点:该点到其它n-1要逆转的道路最少,(边<u,v>,如果v要到u去,则要逆转该边方向)如果有多个这样的点,则升序输出所有 思路: 看了三篇博客,挺好的 http://blog.csdn.net/chl_3205/article/details/9284747 http://m.blog.csdn.net/qq_32570675/article/d