树形dp初步

其实很早之前就学过树形dp,今天总接一下。树形dp就是一个在树上跑的dp(滑稽)

先是一道板子题:树上最大独立集

直接上代码了。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
struct node
{
    int x,y,next;
};
node a[110005];
int len,last[110005];
void add(int x,int y)
{
    a[++len].x = x;
    a[len].y = y;
    a[len].next = last[x];
    last[x] = len;
}
int fa[11000],son[11000];
int f[11000][2];
int v[110000];
/*
f[i][1]代表请i的最大值
f[i][0]代表不请i的最大值 
*/ 
template <class T>
void read(T &x)
{
    char c;
    int op = 0;
    while(c = getchar(),c > '9' || c < '0')
        if(c == '-') op = 1;
    x = c - '0';
    while(c = getchar(),c >= '0' && c <= '9')
        x = x * 10 + c - '0';
    if(op == 1)
        x = -x;
}
void treedp(int x)
{
    f[x][1] = v[x];
    for(int k = last[x];k;k = a[k].next) //相当于dfs 
        treedp(a[k].y);
    for(int k = last[x];k;k = a[k].next)
    {
        int y = a[k].y;
        f[x][1] += f[y][0]; //dp转移式① 
    }
    f[x][0] = 0;
    for(int k = last[x];k;k = a[k].next)
    {
        int y = a[k].y;
        f[x][0] += max(f[y][0],f[y][1]);//dp转移式② 
    }
}
int main()
{
    int n;
    read(n);
    memset(f,-1,sizeof(f));
    memset(fa,0,sizeof(fa));
    for(int i = 1;i <= n;i++)
        read(v[i]);
    int xx,yy;len = 0;
    memset(last,0,sizeof(last));
    while(scanf("%d%d",&xx,&yy) != EOF)
    {
        if(xx == 0 && yy == 0)
        {
            break;
        }
        add(yy,xx);
        fa[xx] = yy; //找根节点 
    }
    int root = 0;
    for(int i = 1;i <= n;i++)
        if(fa[i] == 0)
        {
            root = i;
            break;
        }
    treedp(root);
    printf("%d\n",max(f[root][0],f[root][1]));
    return 0;
}

然后还有几个稍微比这个难一点的题,比如:加分二叉树

【问题描述】
设一个有n个节点的二叉树的中序遍历为(l,2,3,…,n),其中数字1,2,3,…,n为节点编号。
每个节点都有一个分数(均为正整数),记第i个节点的分数为di, 每棵子树都有一个加分,任一棵子树subtree的加分计算方法如下:
    subtree的左子树的加分× subtree的右子树的加分+subtree的根的分数
    若某个子树为空,规定其加分为1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。
    试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树tree。要求输出;
    (1)tree的最高加分
    (2)tree的前序遍历
【输入格式】
    第1行:一个整数n(n<30),为节点个数。
    第2行:n个用空格隔开的整数,为每个节点的分数(分数<100)。
【输出格式】
    第1行:一个整数,为最高加分(结果不会超过2,0 0000 0000)。
    第2行:n个用空格隔开的整数,为该树的前序遍历。
【样例输入】
5
5 7 1 2 10
【样例输出】
145
3 1 2 4 5

这个题需要枚举中间的断点,然后进行dp。dp比之前简单了,但是其他的要难一些。

#include<cstdio>
#include<iostream>
using namespace std;
int root[50][50],d[50],f[50][50];
void pre_visit(int l,int r)
{
    if(l <= r)
    {
        cout<<root[l][r]<<" ";
        pre_visit(l,root[l][r] - 1);
        pre_visit(root[l][r] + 1, r);
    }
}
int main()
{
    int m;
    cin>>m;
    for(int i = 0;i <= m;i++)
    {
        for(int j = 0;j <= m;j++)
        {
            f[i][j] = 1;
        }
    }
    for(int i = 1;i <= m;i++)
    {
        cin>>d[i];
        root[i][i] = i;
        f[i][i] = d[i];
    }
    for(int k = 2;k <= m;k++)
    {
        for(int l = 1;l <= m - k + 1;l++)
        {
            int r = l + k - 1;
            for(int i = l;i <= r;i++)
            {
                if(f[l][r] < f[l][i - 1] * f[i + 1][r] + d[i])
                {
                    root[l][r] = i;
                    f[l][r] = f[l][i - 1] * f[i + 1][r] + d[i];
                }
            }
        }
    }
    cout<<f[1][m]<<endl;
    cout<<root[1][m]<<" ";
    pre_visit(1,root[1][m] - 1);
    pre_visit(root[1][m] + 1,m);
}

还有一个皇宫看守,和最大独立点集很像

【问题描述】
太平王世子事件后,陆小凤成了皇上特聘的御前一品侍卫。 皇宫以午门为起点,直到后宫嫔妃们的寝宫,呈一棵树的形状;有边直接相连的宫殿可以互相望见。大内保卫森严,三步一岗,五步一哨,每个宫殿都要有人全天候看守,在不同的宫殿安排看守所需的费用不同。 可是陆小凤手上的经费不足,无论如何也没法在每个宫殿都安置留守侍卫。
 
编程任务:帮助陆小凤布置侍卫,在看守全部宫殿的前提下,使得花费的经费最少。
【输入格式】 
输入文件中数据表示一棵树,描述如下: 
第1行 n,表示树中结点的数目。 
第2行至第n+1行,每行描述每个宫殿结点信息,依次为:该宫殿结点标号i(0<I<=N),在该宫殿安置侍卫所需的经费K,该点的儿子数M,接下来M个数,分别是这个节点的M个儿子的标号R1,R2,...,RM。对于一个n(0 < n<=1500)个结点的树,结点标号在1到n之间,且标号不重复。 
【输出格式】
输出文件仅包含一个数,为所求的最少的经费。 
输入样例:
6
1 30 3 2 3 4
2 16 2 5 6
3 5 0
4 4 0
5 11 0
6 5 0
输出样例:
25

这个题很好想。

#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
struct node
{
    ll x,y,next;
};
node a[110000];
ll v[100000],f[100000][5];
ll last[200000],len = 0;
bool bk[50000];
/*
i节点安全代表i节点和子树安全
不安全代表i节点不安全,但是子树安全 
f[i][0]表示x点不放人,但是安全
f[i][1]表示x点不放人,不安全
f[i][2]表示x点放人,所以安全
f[i][3]表示x点放人,但是不安全,显然不成立 
*/
void treedp(int x)
{
    f[x][2] = v[x];
    f[x][1] = 0;f[x][0] = 0;
    ll minn = 2147364847;
    bool bkk = false;
    for(int k = last[x];k;k = a[k].next)
    {
        ll y = a[k].y;
        if(bk[y] == false)
        {
            bk[y] = true;
            treedp(y);
            minn = min(f[y][2] - f[y][0],minn);
            f[x][0] += min(f[y][0],f[y][2]);
            if(f[y][2] <= f[y][0])
            {
                bkk = true;
            }//一定选f[i][2] 
            f[x][1] += f[y][0];
            f[x][2] += min(f[y][2],min(f[y][1],f[y][0]));
        }
    }
    if(bkk == false)
    {
        f[x][0] += minn;
    }
}
void add(int x,int y)
{
    a[++len].x = x;
    a[len].y = y;
    a[len].next = last[x];
    last[x] = len;
}
int main()
{
    ll n;
    cin>>n;
    memset(f,0,sizeof(f));
    for(int i = 1;i <= n;i++)
    {
        ll x,m,k;
        cin>>x>>k>>m;
        v[x] = k;
        for(int j = 1;j <= m;j++)
        {
            ll y;
            cin>>y;
            add(x,y);
            add(y,x);
        }
    }
    ll root = 1;
    memset(bk,false,sizeof(bk));
    bk[root] = true;
    treedp(root);
    cout<<min(f[root][0],f[root][2]);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/DukeLv/p/9478497.html