DP(6)--树形DP

树形 DP
树形 DP,即在树上进行的 DP。由于树固有的递归性质,树形 DP 一般都是递归进行的。


链式前向星包含两种结构:
(1)边集数组: edge[], edge[i]表示第i条边;
(2)头结点组数: head[], head[i]存以i为起点的第一条边的下标(在edge[]中的下标)

struct node {
    int to;
    int next;
    int w;
} edge[MAXN*MAXN];

int head[MAXN];

添加一条边(u, v, w)
void add(int u, int v, int w)
{
    edge[cnt].to = v;
    edge[cnt].w = w;
    edge[cnt].next = head[u];  // 采用头插法
    head[u] = cnt++;
}
如果是有向图,每输入一条边,执行一次add(u, v, w)即可; 
如果是无向图,则需要执行两次{add(u, v, w); add(v, u, w)}

/* https://www.luogu.com.cn/problem/P1352 */

#include
using namespace std;

const int MAXN = 6001;
// edge[i]表示第i条边
struct node {
    int to;
    int next;
} edge[MAXN];
// head[i]存以i为起点的第一条边的下标
int head[MAXN];

bool vis[MAXN];
int cnt = 0;
// f[i][]: 以i为根的子树的最优解, f[i][0]: i不参加;f[i][1]: i参加
int f[MAXN][2];
// isLeaf[i]: true, i has one leaf node
bool isLeaf[MAXN];

void add(int u, int v)
{
    edge[cnt].to = v;
    edge[cnt].next = head[u];  // 采用头插法
    head[u] = cnt++;
}

// 从叶子节点开始计算,返回上一层时更新当前结点的最优解
void dfs(int u)
{
    vis[u] = true;
    for (int i = head[u]; ~i; i = edge[i].next)
    {
        int v = edge[i].to;
        if (vis[v])
            continue;
        dfs(v);
        f[u][0] += max(f[v][0], f[v][1]);
        f[u][1] += f[v][0];
    }
    
    return;
}

int main()
{
    int n, i, u, v;
    cin >> n;
    for (i = 1; i <= n; ++i)
    {
        cin >> f[i][1];
        head[i] = -1;
        vis[i] = false;
    }
    for (i = 1; i < n; ++i)
    {
        cin >> v >> u;
        isLeaf[v] = true;
        add(u, v);
    }

    for (i = 1; i <= n; ++i)
        if (!isLeaf[i])
        {
            dfs(i);
            cout << max(f[i][0], f[i][1]) << endl;
        }

    return 0;
}

/* https://www.luogu.com.cn/problem/P3574 */

/*
状态方程
f[i]: i的子树全部安装好游戏的最小时间(包括i本身自己,但不包括root)
若有cnt条边与u相连
for (int i = 1; i <= cnt; ++i)
    f[u] = max(f[u], f[son[i]] + 从u遍历到son[i]所经过路径花费的时间);
遍历子节点规则:结余时间多的先访问
*/
#include
#include
using namespace std;

const int MAXN = 500001;
// edge[i]表示第i条边
struct node {
    int to;
    int next;
} edge[MAXN<<1];
// head[i]存以i为起点的第一条边的下标
int head[MAXN];
int cnt = 0;
 
int f[MAXN]; // f[i]: i的子树全部安装好游戏的最小时间(包括i本身自己,但不包括root)
int g[MAXN]; // g[i]: 遍历i子树的时间(往返路径为2)
int cost[MAXN]; // cost[i]: i节点安装游戏的时间
int son[MAXN];

void add(int u, int v)
{
    edge[cnt].to = v;
    edge[cnt].next = head[u];  // 采用头插法
    head[u] = cnt++;
}

// f[i]-g[i]: 为遍历i子树的结余时间,按结余时间降序排列
bool cmp(int i, int j) 
{
    return (f[i] - g[i]) > (f[j] - g[j]);
}

// 根据结余时间从大到小的顺序,自叶节点更新f[]
void dfs(int u, int fa)
{
    if ( u != 1) // u is not root
        f[u] = cost[u];

    for (int i = head[u]; ~i; i = edge[i].next)
    {
        int v = edge[i].to;
        if (v != fa) // unvisited
            dfs(v, u);
    }

    int num = 0;
    for (int i = head[u]; ~i; i = edge[i].next)
    {
        int v = edge[i].to;
        if (v != fa)
            son[++num] = v;
    }
    
    sort(son+1, son+1+num, cmp);
    // 按结余时间从大到小顺序遍历孩子节点,才能获取到最优解
    for (int i = 1; i <= num; ++i)
    {
        f[u] = max(f[u], f[son[i]] + g[u] + 1);
        g[u] += g[son[i]] + 2;
    }
}

int main()
{
    int i, n, u, v;
    cin >> n;
    for (i = 1; i <= n; ++i)
    {
        cin >> cost[i];
        head[i] = -1;
    }

    for (i = 1; i < n; ++i)
    {
        cin >> u >> v;
        add(u, v);
        add(v, u);
    }

    dfs(1, 0);
    cout << max(f[1], g[1] + cost[1]) << endl;

    return 0;
}

树上背包
/*  https://www.luogu.com.cn/problem/P2014 */

/*
dp[i][j]: i为根的子树选j门课程的最大学分
枚举x节点的每个子结点y,同时枚举以y为根的子树选了几门课程,将子树的结果合并到x上
状态方程: dp[x][j]=max(dp[x][j],dp[x][j−k]+dp[y][k])(k∈[0,j))

新增一门0学分的课程(设这个课程的编号为0),作为所有无先修课课程的先修课,
这样我们就将森林变成了一棵以0号课程为根的树。
*/

#include
#include
using namespace std;

const int MAXN = 301;

// edge[i]表示第i条边
struct node {
    int to;
    int next;
} edge[MAXN];
head[i]存以i为起点的第一条边的下标
int head[MAXN];
int cnt = 0, m;
// dp[i][j]: i为根的子树选j个的最大学分
int dp[MAXN][MAXN];

void add(int u, int v)
{
    edge[cnt].to = v;
    edge[cnt].next = head[u];
    head[u] = cnt++;
}

void dfs(int u)
{
    for (int i = head[u]; ~i; i = edge[i].next)
    {
        int v = edge[i].to;
        dfs(v);
        for (int j = m+1; j >= 1; --j)
            for (int k = 0; k < j; ++k)
                dp[u][j] = max(dp[u][j], dp[u][j-k] + dp[v][k]);
    }
}

int main()
{
    int n, i, p, v;
    memset(head, -1, sizeof(head));
    cin >> n >> m;

    for (i = 1; i <= n; ++i)
    {
        cin >> p >> v;
        dp[i][1] = v;
        add(p, i);
    }

    dfs(0);

    cout << dp[0][m+1] << endl;

    return 0;
}

换根 DP
树形DP中的换根DP通常不会指定根结点,并且根结点的变化会对一些值,例如子结点深度和、点权和等产生影响。
通常需要两次DFS,第一次DFS预处理诸如深度,点权和之类的信息,在第二次DFS开始运行换根动态规划

/* https://www.luogu.com.cn/problem/P3478 */

DP(6)--树形DP_第1张图片

 

#include
#include
using namespace std;

const int MAXN = 1000001;
// edge[i]表示第i条边
struct node {
    int to;
    int next;
} edge[MAXN<<1];
head[i]存以i为起点的第一条边的下标
int head[MAXN<<1];
int cnt = 0, n;
void add(int u, int v)
{
    edge[cnt].to = v;
    edge[cnt].next = head[u];
    head[u] = cnt++;
}

// s[i]: 以i为根的子树中节点的个数
long long s[MAXN];
// f[i]: 以i为根时所有节点的深度之和
long long f[MAXN];
// depth[i]: 以i节点的深度
long long depth[MAXN];

// 初始化,计算s[], depth[]
void dfs(int u, int fa)
{
    s[u] = 1;
    depth[u] = depth[fa] + 1;
    for (int i = head[u]; ~i; i = edge[i].next)
    {
        int v = edge[i].to;
        if (v != fa)
        {
            dfs(v, u);
            s[u] += s[v];
        }
    }
}

// 根节点u变为v,计算f[v]
void swithroot(int u, int fa)
{
    for (int i = head[u]; ~i; i = edge[i].next)
    {
        int v = edge[i].to;
        if (v != fa)
        {
            f[v] = f[u] + n - (s[v]<<1); 
            swithroot(v, u);
        }
    }
}

int main()
{
    int i, u, v;
    memset(head, -1, sizeof(head));
    cin >> n;

    for (i = 1; i < n; ++i)
    {
        cin >> u >> v;
        add(u, v);
        add(v, u);
    }

    // 假设以1作为根节点
    dfs(1, 1);

    for (i = 1; i <= n; ++i)
        f[1] += depth[i];

    swithroot(1, 1);

    long long ans = f[1];
    int res = 1;
    for (i = 2; i <= n; ++i)
        if (ans < f[i])
        {
            ans = f[i];
            res = i;
        }

    cout << res << endl;

    return 0;
}

你可能感兴趣的:(算法,图论,深度优先)