暑假集训 || 树DP

树上DP通常用到dfs https://www.cnblogs.com/mhpp/p/6628548.html

POJ 2342

相邻两点不能同时被选 经典题

f[0][u]表示不选u的情况数,此时v可选可不选

f[1][u]表示选u的情况数,此时v不选

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int SZ = 20100;
const int INF = 1e9+10;
int f[2][SZ];
int head[SZ], nxt[SZ], l[SZ], tot = 0;
void build(int f, int t)
{
    l[++tot] = t;
    nxt[tot] = head[f];
    head[f] = tot;
}
void dfs(int u, int fa)
{
    for(int i = head[u];i;i = nxt[i])
    {
        int v = l[i];
        if(v == fa) continue;
        dfs(v, u);
        f[0][u] += max(f[0][v], f[1][v]);
        f[1][u] += f[0][v];
    }
    return;
}
int main()
{
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) scanf("%d", &f[1][i]);
    while(1)
    {
        int u, v;
        scanf("%d %d", &u, &v);
        if(u == 0 && v == 0) break;
        build(u, v);
        build(v, u);
    }
    dfs(1, 0);
    printf("%d\n", max(f[0][1], f[1][1]));
    return 0;
}

URAL 1018

一个二叉树,每条边上都有一定的苹果,减去一些边使得剩下的苹果最多(要从叶子那儿开始剪

f[p][u]表示在u点及它的子树内一共选择p个点,能剩下的最多苹果数

边权不好处理,把它转移到点上

转移时考虑u点(根节点)一定选,在左儿子和右儿子中一共选p-1个,分别枚举即可

记忆化搜索,每次dfs时若这个f[p][u]之前已经算过,可以直接return这个值

struct Tree
{
    int lc, rc;
}tree[SZ];
queue<int> q;
int vis[SZ], hasc[SZ];
void bfs()
{
    q.push(1);
    vis[1] = 1;
    while(q.size())
    {
        int u = q.front(); q.pop();
        int flag = 0;
        for(int i = head[u];i;i = nxt[i])
        {
            int v = l[i].t;
            hasc[u] = 1;
            if(!vis[v])
            {
                vis[v] = 1;
                a[v] = l[i].d;
                q.push(v);
                if(!flag) tree[u].lc = v;
                if(flag) tree[u].rc = v;
                flag++;
            }
        }
    }
    return;
}

int dfs(int u, int p)//以u为根的子树,保留p个点
{
    if(p == 0) return 0;
    if(f[p][u] > 0) return f[p][u];
    if(!hasc[u]) return a[u];
    for(int i = 0; i < p; i++)
    {
        f[p][u] = max(f[p][u], dfs(tree[u].lc, i) + dfs(tree[u].rc, p-i-1) + a[u]);
    }
    return f[p][u];
}
int main()
{
    scanf("%d %d", &n, &Q);
    for(int i = 0; i < n-1; i++)
    {
        int u, v, w;
        scanf("%d %d %d", &u, &v, &w);
        build(u, v, w);
        build(v, u, w);
    }
    bfs();
    printf("%d\n", dfs(1, Q+1));
    return 0;
}

UVA 1218

选一些点,当一个点被选择的时候,它邻接的点就被覆盖了,要求每个点被覆盖有且仅有一次,问最少选多少个点

最小支配集

https://blog.csdn.net/wyjwyl/article/details/51447427

神奇。。。

const int SZ = 40100;
const int INF = 1e9+10;
int f[3][SZ];
int head[SZ], nxt[SZ], l[SZ], tot = 0;
void build(int f, int t)
{
    l[++tot] = t;
    nxt[tot] = head[f];
    head[f] = tot;
}
/*
f[0][u] u选了
f[1][u] u不选,有一个v选了
f[2][u] u不选,v不选
*/
void dfs(int u, int fa)
{
    int sum = 0, inc = INF;
    bool flag = false;
    for(int i = head[u]; i; i = nxt[i])
    {
        int v = l[i];
        if(v == fa) continue;
        dfs(v, u);
        f[0][u] += (min(f[0][v], f[2][v]));
        if(f[0][v] <= f[1][v])
        {
            sum += f[0][v];
            flag = true;
        }
        else
        {
            sum += f[1][v];
            inc = min(inc, f[0][v] - f[1][v]);
        }
        if(f[1][v] != INF && f[2][u] != INF) f[2][u] += f[1][v];
        else f[2][u] = INF;//
    }
    if(inc == INF && !flag) f[1][u] = INF;
    else
    {
        f[1][u] = sum;
        if(!flag) f[1][u] += inc;
    }
    return;
}
int main()
{
    int n;
    while(1)
    {
        scanf("%d", &n);
        memset(f, 0, sizeof(f));
        memset(head, 0, sizeof(head));
        tot = 0;
        for(int i = 0; i < n-1; i++)
        {
            int u, v;
            scanf("%d %d", &u, &v);
            build(u, v);
            build(v, u);
        }
        for(int i = 1; i <= n; i++) f[0][i] = 1, f[2][i] = 0;
        dfs(1, 0);
        printf("%d\n", min(f[0][1], f[1][1]));
        int tmp;
        scanf("%d", &tmp);
        if(tmp == -1) break;
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/pinkglightning/p/9508586.html