洛谷树形DP(基础)

洛谷树形DP前22题(基础)

xzy的树形dp题单 - 题单 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

P1122 最大子树和 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题意:对一棵树进行删边,判断删完边之后连通块最大权值之和是多少?

定义 f [ c u r ] f[cur] f[cur]为以 c u r cur cur为根的子树权值之和最大值

初始值 f [ c u r ] = w [ c u r ] f[cur] = w[cur] f[cur]=w[cur]

如果他的其中一棵子树大于0,则选择这棵子树。 f[cur] += max(f[p], 0);

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int MAXN = 16100;
int n, id;
int w[MAXN], head[MAXN], f[MAXN], ans = -2147483647;

struct Edge{
    int to, next;
}E[MAXN * 2];

void addedge(int u, int v)
{
    E[id].to = v;
    E[id].next = head[u];
    head[u] = id++;
    return ;
}

void dfs(int cur, int father)
{
    for(int i = head[cur]; i != -1; i = E[i].next)
    {
        int p = E[i].to;
        if(p == father) continue;
        dfs(p, cur);
        f[cur] += max(f[p], 0);
    }
}

int main()
{
    memset(head, -1, sizeof(head));
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &w[i]);
        f[i] = w[i];
    }
    for(int i = 1; i < n; i++)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        addedge(u, v);
        addedge(v, u);
    }
    dfs(1, -1);
    int ans = -2147483647;
    for(int i = 1; i <= n; i++) ans = max(ans, f[i]);
    printf("%d\n", ans);
    return 0;
}

P1352 没有上司的舞会 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

树形DP入门题

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int MAXN = 6e3 + 60;
int n, id;
int w[MAXN], head[MAXN], cnt[MAXN], f[MAXN][2];
struct Edge{
    int to, next;
}E[MAXN * 2];

void addedge(int u, int v)
{
    E[id].to = v;
    E[id].next = head[u];
    head[u] = id++;
    return;
}

void dfs(int cur, int father)
{
    f[cur][0] = 0;
    f[cur][1] = w[cur];
    for(int i = head[cur]; i != -1; i = E[i].next)
    {
        int p = E[i].to;
        if(p == father) continue;
        dfs(p, cur);
        f[cur][0] += max(f[p][0], f[p][1]);
        f[cur][1] += f[p][0];
    }
}

int main()
{
    memset(head, -1, sizeof(head));
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) scanf("%d", &w[i]);
    for(int i = 1; i < n; i++)
    {
        int u, v;
        cin >> u >> v;
        addedge(u, v);
        addedge(v, u);
        cnt[u]++;
        cnt[v]++;
    }

    int root = -1;

    for(int i = 1; i <= n; i++)
    {
        if(cnt[i] == 1) {
            root = i;
            break;
        }
    }

    dfs(root, -1);

    int ans = max(f[root][0], f[root][1]);
    printf("%d\n", ans);

    return 0;
}

P2015 二叉苹果树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

树形背包

f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示以 i i i为子树, j j j为考虑了 j j j个节点,已经选了 k k k条边的最大价值。(p为其子节点)

f [ u ] [ j ] [ k ] = m a x ( f [ u ] [ j − 1 ] [ x ] + f [ p ] [ p 子 结 点 的 数 量 ] [ k − x − 1 ] + w ) f[u][j][k]=max(f[u][j-1][x]+f[p][p子结点的数量][k-x-1]+w) f[u][j][k]=max(f[u][j1][x]+f[p][p][kx1]+w)

可以考虑优化掉中间一维

f [ i ] [ j ] f[i][j] f[i][j]表示以 i i i为子树的结点有 j j j条边。

考虑到一棵树上的结点 u u u与其子节点。

d p [ u ] [ j ] = m a x ( d p [ u ] [ k ] + d p [ p ] [ j − k − 1 ] + w ) dp[u][j] = max(dp[u][k] + dp[p][j - k - 1]+w) dp[u][j]=max(dp[u][k]+dp[p][jk1]+w)

这是很明显的树形背包。

注意枚举背包容量的时候需要逆序枚举(相当于01背包的优化)(先枚举背包容量,再枚举决策)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int MAXN = 105;
int n, q, id, ans;
int head[MAXN], f[MAXN][MAXN];
struct Edge{
    int to, next, w;
}E[MAXN * 2];

void addedge(int u, int v, int w)
{
    E[id].to = v;
    E[id].w = w;
    E[id].next = head[u];
    head[u] = id++;
    return;
}

void dfs(int u, int fa)
{
    f[u][0] = 0;
    for(int i = head[u]; i != -1; i = E[i].next)
    {
        int p = E[i].to, w = E[i].w;
        if(p == fa) continue;
        dfs(p, u);
        for(int i = q; i >= 1; i--)
        {
            for(int j = 0; j < i; j++)
            {
                f[u][i] = max(f[u][i], f[u][j] + f[p][i - j - 1] + w);
            }
        }
    }
}

int main()
{
    memset(head, -1, sizeof(head));
    scanf("%d%d", &n, &q);
    for(int i = 1; i < n; i++)
    {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        addedge(u, v, w);
        addedge(v, u, w);
    }
    dfs(1, -1);
    ans = f[1][q];
    /*
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= q; j++)
            printf("%d ", f[i][j]);
        printf("\n");
    }*/
    printf("%d\n", ans);
    return 0;
}

[P2014 CTSC1997]选课 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

同上题

题意:选课就是有依赖的树形背包只有父节点被选,子节点才能被选择。

与上一题不同的是此道题会出现多棵树

解决办法:添加一个0号结点,0号结点权值为0必选得选择。为所有根节点的父节点。就可以将多棵树的问题转化到一棵树上解决。

最后答案求选m门课,即是 f [ 0 ] [ m + 1 ] f[0][m+1] f[0][m+1]的值

f [ u ] [ k ] = m a x ( f [ u ] [ j ] + f [ p ] [ k − j ] ) f[u][k] = max(f[u][j]+f[p][k-j]) f[u][k]=max(f[u][j]+f[p][kj])

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int MAXN = 305;
int n, m, id;
int k[MAXN], head[MAXN], f[MAXN][MAXN];
struct Edge{
    int to, next;
}E[MAXN * 2];

void addedge(int u, int v)
{
    E[id].next = head[u];
    E[id].to = v;
    head[u] = id++;
    return;
}

void dfs(int u, int fa)
{
    f[u][1] = k[u];
    for(int i = head[u]; i != -1; i = E[i].next)
    {
        int p = E[i].to;
        if(p == fa) continue;
        dfs(p, u);
        for(int i = m + 1; i >= 1; i--)
        {
            for(int j = 1; j <= i; j++)
            {
                f[u][i] = max(f[u][i], f[u][j] + f[p][i - j]);
            }
        }
    }
}

int main()
{
    memset(head, -1, sizeof(head));
    scanf("%d%d", &n, &m);
    k[0] = 0;
    for(int i = 1; i <= n; i++)
    {
        int s;
        scanf("%d%d", &s, &k[i]);
        addedge(i, s);
        addedge(s, i);
    }

    dfs(0, -1);
    /*
    for(int i = 0; i <= n; i++)
    {
        for(int j = 0; j <= m + 1; j++)
        {
            printf("%d ", f[i][j]);
        }
        printf("\n");
    }
    */

    printf("%d\n", f[0][m + 1]);

    return 0;
}

P2016 战略游戏 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

树上最小点覆盖

f[u][0] += f[p][1];
f[u][1] += min(f[p][0], f[p][1]);

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int MAXN = 1590;
int n, id;
int head[MAXN], f[MAXN][2];
struct Edge{
    int to, next;
}E[MAXN * 2];

void addedge(int u, int v)
{
    E[id].to = v;
    E[id].next = head[u];
    head[u] = id++;
}

void dfs(int u, int fa)
{
    f[u][0] = 0;
    f[u][1] = 1;
    for(int i = head[u]; i != -1; i = E[i].next)
    {
        int p = E[i].to;
        if(p == fa) continue;
        dfs(p, u);
        f[u][0] += f[p][1];
        f[u][1] += min(f[p][0], f[p][1]);
    }
}

int main()
{
    memset(head, -1, sizeof(head));
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        int x, num;
        cin >> x >> num;
        for(int j = 1; j <= num; j++) {
            int u;
            cin >> u;
            addedge(u, x);
            addedge(x, u);
        }
    }
    dfs(1, -1);
    printf("%d\n", min(f[1][0], f[1][1]));
    return 0;
}

[P3478 POI2008]STA-Station - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)(换根DP)

题意:给定一棵树,求以树中某个点为根结点的所有结点深度之和

换根DP思想:第一遍dfs可以求出根节点的信息,第二遍dfs根据父节点的信息推出子节点的信息。

第一次dfs可以求出根节点的所有点深度值和(求深度为顺序往下)(以当前点为根的子结点个数是反dfs序)

考虑这么一件事:如果把当前根节点的子节点作为根,则以当前子节点为根的点(含自身)深度-1,其他点深度+1

这个顺序是从上往下更改的

f [ p ] = f [ u ] − s z [ p ] + n − s z [ p ] f[p]=f[u]-sz[p]+n-sz[p] f[p]=f[u]sz[p]+nsz[p]

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
typedef long long ll;
const int MAXN = 1e6 + 60;
int n, id;
ll head[MAXN], f[MAXN], depth[MAXN], sz[MAXN];
struct Edge{
    int to, next;
}E[MAXN * 2];

void addedge(int u, int v)
{
    E[id].to = v;
    E[id].next = head[u];
    head[u] = id++;
    return;
}

void dfs1(int u, int fa)
{
    sz[u] = 1;
    depth[u] = depth[fa] + 1;
    for(int i = head[u]; i != -1; i = E[i].next)
    {
        int p = E[i].to;
        if(p == fa) continue;
        dfs1(p, u);
        sz[u] += sz[p];
    }
}

void dfs2(int u, int fa)
{
    for(int i = head[u]; i != -1; i = E[i].next)
    {
        int p = E[i].to;
        if(p == fa) continue;
        f[p] = f[u] - sz[p] + (n - sz[p]);
        dfs2(p, u);
    }
}

int main()
{
    memset(head, -1, sizeof(head));
    cin >> n;
    for(int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        addedge(u, v);
        addedge(v, u);
    }
    dfs1(1, 0);
    for(int i = 1; i <= n; i++) f[1] += depth[i];
    dfs2(1, 0);
    ll ans = 0;
    int res;
    for(int i = 1; i <= n; i++) {
        if(ans < f[i]) {
            ans = f[i];
            res = i;
        }
    }

    printf("%d\n", res);

    return 0;
}

[P1040 NOIP2003 提高组] 加分二叉树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

区间DP

对于一个序列,它左边的数构成了它的左子树,它右边的数构成了它的右子树。

f [ l ] [ r ] = f [ l ] [ k − 1 ] ∗ f [ k + 1 ] [ r ] + f [ k ] [ k ] f[l][r]=f[l][k-1]*f[k+1][r]+f[k][k] f[l][r]=f[l][k1]f[k+1][r]+f[k][k]

注意左子树与右子树为空的情况

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
typedef long long ll;
const int MAXN = 32;
int n;
ll w[MAXN], f[MAXN][MAXN], root[MAXN][MAXN];

void print(int l, int r)
{
    if(l > r) return;
    int ro = root[l][r];
    printf("%lld ", ro);
    print(l, ro - 1);
    print(ro + 1, r);
}

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++) {
        scanf("%lld", &w[i]);
        f[i][i] = w[i];
        root[i][i] = i;
    }
    for(int len = 2; len <= n; len ++)
    {
        for(int l = 1; l + len - 1 <= n; l ++)
        {
            int r = l + len - 1;
            for(int k = l; k <= r; k ++)
            {
                if(k == l) {
                    if(f[l][r] < f[l + 1][r] + f[l][l])
                    {
                        f[l][r] = f[l + 1][r] + f[l][l];
                        root[l][r] = l;
                    }
                }
                else if(k == r) {
                    if(f[l][r] < f[l][r - 1] + f[r][r])
                    {
                        f[l][r] = max(f[l][r], f[l][r - 1] + f[r][r]);
                        root[l][r] = r;
                    }
                }
                else {
                    if(f[l][r] < f[l][k - 1] * f[k + 1][r] + f[k][k])
                    {
                        f[l][r] = f[l][k - 1] * f[k + 1][r] + f[k][k];
                        root[l][r] = k;
                    }
                }
            }
        }
    }
    printf("%lld\n", f[1][n]);
    print(1, n);
    cout << endl;
    return 0;
}

CF219D Choosing Capital for Treeland - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)(换根DP)

题意:给定一棵树,所有边都是单向边。首都定义为到其他点都有一条路径。问选取哪一个点为首都,能使翻转的边数最少。

存图的时候存双向边,存在的边设为1,不存在设为0。

第一次dfs的时候,从下到上计算结点1所要的反转的边是多少。

第二次dfs的时候,从上往下,判断当前子节点与父节点连的边的方向,来确定 f [ p ] = f [ u ] − 1 或 是 f [ u ] + 1 f[p] = f[u]-1或是f[u]+1 f[p]=f[u]1f[u]+1

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int MAXN = 2e5 + 50;
int n, id;
int head[MAXN], f[MAXN];
struct Edge{
    int to, next, w;
}E[MAXN * 2];

void addedge(int u, int v, int w)
{
    E[id].to = v;
    E[id].w = w;
    E[id].next = head[u];
    head[u] = id++;
    return;
}

void dfs1(int u, int fa)
{
    for(int i = head[u]; i != -1; i = E[i].next)
    {
        int p = E[i].to, w = E[i].w;
        if(p == fa) continue;
        dfs1(p, u);
        f[u] += (f[p] + w);
    }
}

void dfs2(int u, int fa)
{
    for(int i = head[u]; i != -1; i = E[i].next)
    {
        int p = E[i].to, w = E[i].w;
        if(p == fa) continue;
        if(w == 0) f[p] = f[u] + 1;
        else f[p] = f[u] - 1;
        dfs2(p, u);
    }
}

int main()
{
    memset(head, -1, sizeof(head));
    scanf("%d", &n);
    for(int i = 1; i < n; i++)
    {
        int u, v;
        cin >> u >> v;
        addedge(u, v, 0);
        addedge(v, u, 1);
    }
    dfs1(1, -1);
    // printf("%d\n", f[1]);
    dfs2(1, -1);
    int minn = f[1];
    for(int i = 1; i <= n; i++) minn = min(minn, f[i]);
    printf("%d\n", minn);
    for(int i = 1; i <= n; i++) {
        if(minn == f[i]) printf("%d ", i);
    }
    printf("\n");

    return 0;
}

P1270 “访问”美术馆 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题意:一个画廊,从大厅开始,通过每一条边都需要一定的时间,画廊里面有画,拿一幅画需要5s,问在规定时间内最多能拿走多少张画。

树形背包的问题:

数组要开的足够大。

小偷最后需要从大门出去,所以通过每条路的时间为 t i m e ∗ 2 time*2 time2

不能恰好在规定时间内出去,规定时间-1。

难度在于建图:

采用的是递归建图的方式

如果有画都让当前节点与0相连,初始化的时候只需让 f [ 0 ] [ 0 ] = 1 f[0][0] = 1 f[0][0]=1

树形背包 f [ u ] [ j ] f[u][j] f[u][j]表示从到 u u u个结点,花了 j j j秒钟,偷画的最大数量

同样是逆序枚举

f [ u ] [ j ] = m a x ( f [ u ] [ j − k − w ] + f [ p ] [ k ] ) f[u][j] = max(f[u][j-k-w]+f[p][k]) f[u][j]=max(f[u][jkw]+f[p][k])

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int M = 105, N = 2010;
int s, id, tot, root;
int head[N], f[N][N];

struct Edge{
    int to, next, w;
}E[M * 2];

void addedge(int u, int v, int w)
{
    E[id].to = v;
    E[id].w = w;
    E[id].next = head[u];
    head[u] = id++;
    return;
}

void input(int u, int fa)
{
    int time, num;
    scanf("%d%d", &time, &num);
    addedge(fa, u, time * 2);
    if(!num)
    {
        input(++tot, u);
        input(++tot, u);
    }
    else {
        while(num)
        {
            addedge(u, 0, 5);
            num--;
        }
    }
}

void dfs(int u, int fa)
{
    for(int i = head[u]; i != -1; i = E[i].next)
    {
        int p = E[i].to, w= E[i].w;
        if(p == fa) continue;
        dfs(p, u);
        for(int j = s; j >= w; j --)
        {
            for(int k = 0; k <= j - w; k++)
            {
                f[u][j] = max(f[u][j], f[u][j - k - w] + f[p][k]);
            }
        }
    }
}

int main()
{
    memset(head, -1, sizeof(head));
    cin >> s;
    s -- ;

    root = tot = 1;
    input(++tot, root);

    f[0][0] = 1;

    dfs(root, -1);
    printf("%d\n", f[root][s]);

    return 0;
}

[P1131 ZJOI2007] 时态同步 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

调整子树的时候,调整父节点比调整子节点可以用更少的道具

因此对于每个节点 u u u只需要判断,离它最远的子节点的距离为多少,最后自底向上进行更新。

记得开long long

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
typedef long long ll;
const int MAXN = 5e5 + 50;
ll head[MAXN], f[MAXN];
ll n, root, id, ans;

struct Edge{
    int to, next, w;
}E[MAXN * 2];

void addedge(int u, int v, int w)
{
    E[id].w = w;
    E[id].to = v;
    E[id].next = head[u];
    head[u] = id++;
    return;
}

void dfs(int u, int fa)
{
    for(int i = head[u]; i != -1; i = E[i].next)
    {
        int p = E[i].to, w = E[i].w;
        if(p == fa) continue;
        dfs(p, u);
        f[u] = max(f[u], f[p] + w);
    }
    for(int i = head[u]; i != -1; i = E[i].next)
    {
        int p = E[i].to, w = E[i].w;
        if(p == fa) continue;
        ans += f[u] - (f[p] + w);
    }
}

int main()
{
    memset(head, -1, sizeof(head));
    scanf("%d%d", &n, &root);
    for(int i = 1; i < n; i++)
    {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        addedge(u, v, w);
        addedge(v, u, w);
    }
    dfs(root, -1);
    cout << ans << endl;
    return 0;
}

[P2279 HNOI2003]消防局的设立 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

一个点可以覆盖与他距离为二的两个点,问一棵树上最少多少点能覆盖整棵树

自底向上进行贪心,每次都覆盖暂未覆盖结点的爷爷结点(用一个优先队列来维护)

第一次dfs求出深度,第二次dfs模拟每一次覆盖。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>

using namespace std;
typedef pair<int, int> PII;
const int MAXN = 1010;
int n, id;
int head[MAXN], f[MAXN], d[MAXN];
bool vis[MAXN];
struct Edge{
    int to, next;
}E[MAXN * 2];
priority_queue<PII>pq;

void addedge(int u, int v)
{
    E[id].to = v;
    E[id].next = head[u];
    head[u] = id++;
}

void dfs1(int u, int fa)
{
    d[u] = d[fa] + 1;
    pq.push(make_pair(d[u], u));
    for(int i = head[u]; i != -1; i = E[i].next)
    {
        int p = E[i].to;
        if(p == fa) continue;
        dfs1(p, u);
    }
}

void dfs2(int u, int d)
{
    if(d > 2) return;
    vis[u] = true;
    for(int i = head[u]; i != -1; i = E[i].next)
    {
        int p = E[i].to;
        dfs2(p, d + 1);
    }
}

int main()
{
    memset(head, -1, sizeof(head));
    scanf("%d", &n);
    for(int i = 2; i <= n; i++)
    {
        scanf("%d", &f[i]);
        addedge(i, f[i]);
        addedge(f[i], i);
    }
    dfs1(1, -1);
    memset(vis, false, sizeof(vis));
    int ans = 0;
    while(!pq.empty())
    {
        while(!pq.empty() && vis[pq.top().second]) pq.pop();
        if(pq.empty()) break;
        auto u = pq.top();
        int d = u.first, id = u.second;
        dfs2(f[f[id]], 0);
        pq.pop();
        ans++;
    }

    printf("%d\n", ans);
    return 0;
}

貌似还有一种树形DP状态机的思想

P1273 有线电视网 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)(树形背包)

d p [ u ] [ i ] [ j ] dp[u][i][j] dp[u][i][j]表示第 u u u个结点,考虑其前 i i i个儿子,联通 j j j个居民最大的收益。

d p [ u ] [ i ] [ j ] = m a x ( d p [ u ] [ i ] [ j ] , d p [ u ] [ i − 1 ] [ j − k ] + d p [ v ] [ f u l l s o n [ v ] ] [ k ] − w ) dp[u][i][j] = max(dp[u][i][j], dp[u][i-1][j-k] + dp[v][fullson[v]][k]-w) dp[u][i][j]=max(dp[u][i][j],dp[u][i1][jk]+dp[v][fullson[v]][k]w)

背包逆序枚举优化成二维

d p [ u ] [ j ] = m a x ( d p [ u ] [ j ] , d p [ u ] [ j − k ] + d p [ v ] [ k ] − w ) dp[u][j] = max(dp[u][j], dp[u][j-k]+dp[v][k]-w) dp[u][j]=max(dp[u][j],dp[u][jk]+dp[v][k]w)

注意:

这种树形背包容量的上限为子节点的个数(需要函数中返回(设置成int类型,返回其子结点的个数))

枚举决策的时候是子树大小 t t t与背包空间 j j j取min,即 m i n ( t , j ) min(t,j) min(t,j)

叶结点需要单独考虑

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int MAXN = 3060;
int n, m, id;
int head[MAXN], ww[MAXN], dp[MAXN][MAXN];

struct Edge{
    int to, next, w;
}E[MAXN * 2];

void addedge(int u, int v, int w)
{
    E[id].w = w;
    E[id].to = v;
    E[id].next = head[u];
    head[u] = id++;
}

int dfs(int u, int fa)
{
    if(u > n - m)
    {
        dp[u][0] = 0;
        dp[u][1] = ww[u];
        return 1;
    }
    dp[u][0] = 0;
    int leaf = 0;
    for(int i = head[u]; i != -1; i = E[i].next)
    {
        int p = E[i].to, w = E[i].w;
        if(p == fa) continue;
        int t = dfs(p, u);
        leaf += t;
        for(int j = leaf; j >= 0; j --)
        {
            for(int k = 0; k <= min(j, t); k++)
            {
                dp[u][j] = max(dp[u][j], dp[u][j - k] + dp[p][k] - w);
            }
        }
    }
    return leaf;
}

int main()
{
    memset(head, -1, sizeof(head));
    memset(dp, -0x3f, sizeof(dp));

    cin >> n >> m;
    for(int i = 1; i <= n - m; i++)
    {
        int num;
        cin >> num;
        for(int j = 1; j <= num; j++)
        {
            int v, w;
            cin >> v >> w;
            addedge(i, v, w);
            addedge(v, i, w);
        }
    }

    for(int j = n - m + 1; j <= n; j++) cin >> ww[j];

    dfs(1, -1);
    int ans = -1;

    for(int i = m; i >= 0; i--)
    {
        if(dp[1][i] >= 0) {
            ans = i;
            printf("%d\n", ans);
            break;
        }
    }

    return 0;
}

[P2899 USACO08JAN]Cell Phone Network G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)(状态机)

题意:覆盖一棵树,对于任意一个点只要保证他的父节点,子节点,他自己有一个点被覆盖即可。

状态机思想

d p [ u ] [ 0 ] dp[u][0] dp[u][0],表示自己本身被覆盖

d p [ u ] [ 1 ] dp[u][1] dp[u][1],表示它的子节点被覆盖

d p [ u ] [ 2 ] dp[u][2] dp[u][2],表示它的父节点被覆盖

dp[u][0] = sum(min(dp[p][0], dp[p][1], dp[p][2]));
dp[u][1] = dp[x][0] + sum(min(dp[p][0], dp[p][1]));
dp[u][2] = sum(min(dp[p][0], dp[p][1]));
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int MAXN = 10010;
int n, m, id;
int head[MAXN], ww[MAXN], dp[MAXN][3];

struct Edge{
    int to, next;
}E[MAXN * 2];

void addedge(int u, int v)
{
    E[id].to = v;
    E[id].next = head[u];
    head[u] = id++;
}

void dfs(int u, int fa)
{
    int sum = 0;
    dp[u][0] = 1;
    for(int i = head[u]; i != -1; i = E[i].next)
    {
        int p = E[i].to;
        if(p == fa) continue;
        dfs(p, u);
        dp[u][0] += min(dp[p][0], min(dp[p][1], dp[p][2]));
        dp[u][2] += min(dp[p][0], dp[p][1]);
        sum += min(dp[p][0], dp[p][1]);
    }
    dp[u][1] = 1e9;
    for(int i = head[u]; i != -1; i = E[i].next)
    {
        int p = E[i].to;
        dp[u][1] = min(dp[u][1], sum - min(dp[p][1], dp[p][0]) + dp[p][0]);
    }
}

int main()
{
    memset(head, -1, sizeof(head));
    cin >> n;
    for(int i = 1; i < n; i++)
    {
        int u, v;
        cin >> u >> v;
        addedge(u, v);
        addedge(v, u);
    }

    dfs(1, -1);
    printf("%d\n", min(dp[1][0], dp[1][1]));

    return 0;
}

[P4084 USACO17DEC]Barn Painting G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

给一棵树染色,已经有点被染过色。

d p [ i ] [ 1 / 2 / 3 ] dp[i][1/2/3] dp[i][1/2/3]表示第 i i i个结点,被染成第1/2/3种颜色。

dp[i][1] *= (dp[p][2] + dp[p][3])

dp[i][2] *= (dp[p][1] + dp[p][3])

dp[i][3] *= (dp[p][1] + dp[p][2])

注意初始化

被染过色的只有 d p [ u ] [ c o l o r [ u ] ] = 1 dp[u][color[u]]=1 dp[u][color[u]]=1

没被染过色的都是1

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>

using namespace std;
typedef long long ll;
const int MAXN = 100100, mod = 1e9 + 7;
int n, m, id;
ll ans;
ll head[MAXN], dp[MAXN][4], color[MAXN];

struct Edge{
    int to, next;
}E[MAXN * 2];

void addedge(int u, int v)
{
    E[id].to = v;
    E[id].next = head[u];
    head[u] = id++;
}

void dfs(int u, int fa)
{
    if(color[u])
    {
        dp[u][color[u]] = 1;
    }
    else {
        for(int i = 1; i <= 3; i++)
        {
            dp[u][i] = 1;
        }
    }
    for(int i = head[u]; i != -1; i = E[i].next)
    {
        int p = E[i].to;
        if(p == fa) continue;
        dfs(p, u);
        dp[u][1] = dp[u][1] * ((dp[p][2] + dp[p][3]) % mod) % mod;
        dp[u][2] = dp[u][2] * ((dp[p][1] + dp[p][3]) % mod) % mod;
        dp[u][3] = dp[u][3] * ((dp[p][1] + dp[p][2]) % mod) % mod;
    }
}

int main()
{
    memset(head, -1, sizeof(head));
    cin >> n >> m;
    for(int i = 1; i < n; i++)
    {
        int u, v;
        cin >> u >> v;
        addedge(u, v);
        addedge(v, u);
    }
    for(int i = 1; i <= m; i++)
    {
        int k, c;
        cin >> k >> c;
        color[k] = c;
    }
    dfs(1, -1);
    ans = (dp[1][1] + dp[1][2] + dp[1][3]) % mod;
    printf("%lld\n", ans);
    return 0;
}

[P2986 USACO10MAR]Great Cow Gathering G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)(换根DP)

先求出1号结点的路径之和

然后根据 f [ p ] = f [ u ] − s z [ p ] ∗ w + ( c n t − s z [ p ] ) ∗ w f[p]=f[u]-sz[p]*w+(cnt-sz[p])*w f[p]=f[u]sz[p]w+(cntsz[p])w

遍历所有最小值得出答案

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>

using namespace std;
typedef long long ll;
const int MAXN = 100100, mod = 1e9 + 7;
ll n, m, id, cnt;
ll head[MAXN], f[MAXN], num[MAXN], sz[MAXN];

struct Edge{
    int to, next, w;
}E[MAXN * 2];

void addedge(int u, int v, int w)
{
    E[id].to = v;
    E[id].next = head[u];
    E[id].w = w;
    head[u] = id++;
}

void dfs1(int u, int fa)
{
    sz[u] = num[u];
    for(int i = head[u]; i != -1; i = E[i].next)
    {
        int p = E[i].to, w = E[i].w;
        if(p == fa) continue;
        dfs1(p, u);
        sz[u] += sz[p];
        f[u] += (f[p] + sz[p] * w);
    }
}

void dfs2(int u, int fa)
{
    for(int i = head[u]; i != -1; i = E[i].next)
    {
        int p = E[i].to, w = E[i].w;
        if(p == fa) continue;
        f[p] = f[u] - sz[p] * w + (cnt - sz[p]) * w;
        dfs2(p, u);
    }
}

int main()
{
    memset(head, -1, sizeof(head));
    cin >> n;
    for(int i = 1; i <= n; i++) {
        scanf("%lld", &num[i]);
        cnt += num[i];
    }
    for(int i = 1; i < n; i++)
    {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        addedge(u, v, w);
        addedge(v, u, w);
    }
    dfs1(1, -1);
    dfs2(1, -1);

    ll ans = f[1];
    for(int i = 1; i <= n; i++) ans = min(ans, f[i]);
    cout << ans << endl;

    return 0;
}

[P4438 HNOI/AHOI2018]道路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

先读题。

这个题给出了一棵树,在这棵树里,乡村的点都没有儿子,而城市的点都有两个儿子。所以?

很据题意,可以设 f i , j , k f_{i,j,k} fi,j,k,表示从根节点走到序号为 i 的点,通过了 j 条向左走的未修的路(走向左儿子的路),以及 k 条向右走的未修的路(走向右儿子的路)时,以 i 为节点的子树的最小不便利值。

这个数组一开出来,就有浓厚的 DP气息了。 当 i 是一个乡村时,显然能直接求出 f i , j , k = c i ( a i + j ) ( b i + k ) f_{i,j,k}=c_i(a_i+j)(b_i+k) fi,j,k=ci(ai+j)(bi+k) 。而ii是一个城市,则设 l i l_i li为 i 的左儿子, r i r_i ri 为 i 的右儿子。有两种情况:

1.修向左走的路。则有:

f i , j , k = f l i , j , k + f r i , j , k + 1 f_{i,j,k}=f_{l_i,j,k}+f_{r_i,j,k+1} fi,j,k=fli,j,k+fri,j,k+1

2.修向右走的路。则有:

f i , j , k = f l i , j + 1 , k + f r i , j , k f_{i,j,k}=f_{l_i,j+1,k}+f_{r_i,j,k} fi,j,k=fli,j+1,k+fri,j,k

两者取其 min ,方能得解。

最后的答案?当然是 f 1 , 0 , 0 f_{1,0,0} f1,0,0 了。

#include<cstdio>  
#include<cstring>  
#include<algorithm>  
#define LL long long  
using namespace std;  
    int n;  
    struct node{LL x,y,z;} a[20010];  
    int son[20010][5];  
    LL f[20010][45][45];  
LL dfs(int x,int p,int q)  
{  
    if(x>=n) return a[x-n+1].z*(a[x-n+1].x+p)*(a[x-n+1].y+q);  
    if(f[x][p][q]!=f[n+1][41][41]) return f[x][p][q];  
    return f[x][p][q]=min(dfs(son[x][0],p,q)+dfs(son[x][1],p,q+1),dfs(son[x][1],p,q)+dfs(son[x][0],p+1,q));  
}  
int main()  
{  
    int x,y;  
    scanf("%d",&n);  
    memset(f,63,sizeof(f));  
    for(int i=1;i<n;i++)  
    {  
        scanf("%d %d",&x,&y);  
        if(x<0) x=-x+n-1;  
        if(y<0) y=-y+n-1;  
        son[i][0]=x;  
        son[i][1]=y;  
    }  
    for(int i=1;i<=n;i++)  
        scanf("%lld %lld %lld",&a[i].x,&a[i].y,&a[i].z);  
    printf("%lld",dfs(1,0,0));  
}

[P3047 USACO12FEB]Nearby Cows G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)(换根DP)

给你一棵 n个点的树,点带权,对于每个节点求出距离它不超过 k 的所有节点权值和 m i m_i mi

f [ u ] [ i ] f[u][i] f[u][i]表示结点 u u u,并且距离为 i i i的权值之和

dfs1计算根节点的权值

dfs2根据容斥原理计算,距离子节点 p p p距离为 x x x的点是距离父节点 u u u距离为 x − 1 x-1 x1的点,但是 p p p的子树被重复计算。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>

using namespace std;


typedef long long ll;
const int MAXN = 100100, mod = 1e9 + 7;
int n, m, id, cnt;
int head[MAXN], f[MAXN][25], c[MAXN], d[MAXN];


struct Edge{
    int to, next;
}E[MAXN * 2];


void addedge(int u, int v)
{
    E[id].to = v;
    E[id].next = head[u];
    head[u] = id++;
}


void dfs1(int u, int fa)
{
    f[u][0] = c[u];
    d[u] = max(d[u], d[fa] + 1);
    for(int i = head[u]; i != -1; i = E[i].next)
    {
        int p = E[i].to;
        if(p == fa) continue;
        dfs1(p, u);
        for(int j = 1; j <= m; j++) f[u][j] += f[p][j - 1];
    }
}


void dfs2(int u, int fa)
{
    for(int i = head[u];i != -1; i = E[i].next)
    {
        int p = E[i].to;
        if(p == fa) continue;
        for(int j = m; j >= 2; j--)
        {
            f[p][j] -= f[p][j - 2];
        }
        for(int j = 1; j <= m; j++)
        {
            f[p][j] += f[u][j - 1];
        }
        dfs2(p, u);
    }
}


int main()
{

    memset(head, -1, sizeof(head));
    cin >> n >> m;
    for(int i = 1; i < n; i++)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        addedge(u, v);
        addedge(v, u);
    }
    for(int i = 1; i <= n; i++) scanf("%d", &c[i]);

    dfs1(1, -1);
    dfs2(1, -1);

    for(int i = 1; i<= n; i++)
    {
        int ans = 0;
        for(int j = 0; j <= m; j++)
        {
            ans += f[i][j];
        }
        printf("%d\n", ans);
    }


    return 0;
}

[P2585 ZJOI2006]三色二叉树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

f [ u ] [ 0 / 1 ] f[u][0/1] f[u][0/1]表示结点 u u u染成绿色或不染成绿色的最大值

f [ u ] [ 0 ] = f [ l e f t [ u ] ] [ 1 ] + f [ r i g h t [ u ] [ 1 ] ] f[u][0]=f[left[u]][1]+f[right[u][1]] f[u][0]=f[left[u]][1]+f[right[u][1]]

f [ u ] [ 1 ] = m a x ( f [ l e f t [ u ] ] [ 0 ] + f [ r i g h t [ u ] ] [ 1 ] , f [ l e f t [ u ] ] [ 1 ] + f [ r i g h t [ u ] ] [ 0 ] ) f[u][1]=max(f[left[u]][0]+f[right[u]][1],f[left[u]][1]+f[right[u]][0]) f[u][1]=max(f[left[u]][0]+f[right[u]][1],f[left[u]][1]+f[right[u]][0])

对于第i个节点不染绿色的情况,不存在两个节点都不染绿色的情况

因为然而根据抽屉原理,三个点染上两种颜色,一定有两个点的颜色相同。

不符合题意

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;

const int N = 1000000;
int n, ch[N][2], f[N][3], g[N][3], tot = 0;
string a;

int build()
{
	int now = ++tot; //新建节点编号
	if(a[now - 1] == '2') ch[now][0] = build(), ch[now][1] = build();
	else if(a[now - 1] == '1') ch[now][0] = build();
	return now; //返回节点编号
}

void dfs(int x)
{
	int l = ch[x][0], r = ch[x][1];
	if(l) dfs(l); if(r) dfs(r); //递归两个儿子
    if(!l && !r) //叶子节点初始化
    	f[x][0] = g[x][0] = 1, f[x][1] = f[x][2] = g[x][1] = g[x][2] = 0;
	//转移方程
	f[x][0] = max(f[l][1] + f[r][2], f[l][2] + f[r][1]) + 1;
	f[x][1] = max(f[l][0] + f[r][2], f[l][2] + f[r][0]);
	f[x][2] = max(f[l][0] + f[r][1], f[l][1] + f[r][0]);
	g[x][0] = min(g[l][1] + g[r][2], g[l][2] + g[r][1]) + 1;
	g[x][1] = min(g[l][0] + g[r][2], g[l][2] + g[r][0]);
	g[x][2] = min(g[l][0] + g[r][1], g[l][1] + g[r][0]);
}

int main()
{
	cin >> a; n = a.size(); //树的节点数等于字符串长度
	memset(ch, 0, sizeof(ch)); //若一个节点没有该儿子,这个位置的编号为0
	f[0][0] = f[0][1] = f[0][2] = 0; //初始化编号为0的节点
	dfs(build());
	printf("%d", max(f[1][0], max(f[1][1], f[1][2]))); //三种染色取最大值
	printf(" %d", min(g[1][0], min(g[1][1], g[1][2]))); //三种染色取最小值
	return 0;
} 

[P2458 SDOI2006]保安站岗 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

没写

模板题

求树上最小点覆盖问题

f [ u ] [ 0 / 1 / 2 ] f[u][0/1/2] f[u][0/1/2]表示自身被覆盖,子节点被覆盖,父节点被覆盖

UVA1218 完美的服务 Perfect Service - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)(状态机)

没写

题意:一个网络中有 N 个节点,由 N-1 条边连通,每个节点是服务器或者客户端。如果节点 u 是客户端,就意味着 u 所连接的所有点中有且仅有一台服务器。求最少要多少台服务器才能满足要求。

d p [ u ] [ 0 ] dp[u][0] dp[u][0]表示 u u u是服务器,其儿子是不是服务器无所谓

d p [ u ] [ 1 ] dp[u][1] dp[u][1]表示 u u u不是服务器,它父节点是服务器,它的子节点不能是服务器

d p [ u ] [ 2 ] dp[u][2] dp[u][2]表示 u u u及其父节点不是服务器,其子节点必须有一个

d p [ u ] [ 0 ] = s u m ( m i n ( d p [ p ] [ 0 ] , d p [ p ] [ 1 ] ) + + 1 dp[u][0] = sum(min(dp[p][0], dp[p][1])+ + 1 dp[u][0]=sum(min(dp[p][0],dp[p][1])++1

d p [ u ] [ 1 ] = s u m ( d p [ p ] [ 2 ] ) dp[u][1]=sum(dp[p][2]) dp[u][1]=sum(dp[p][2])

d p [ u ] [ 2 ] = s u m ( d p [ p ] [ 2 ] ) + d p [ x ] [ 0 ] − d p [ x ] [ 2 ] dp[u][2]=sum(dp[p][2])+dp[x][0]-dp[x][2] dp[u][2]=sum(dp[p][2])+dp[x][0]dp[x][2]

P1272 重建道路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)(树形背包)

d p [ k ] [ i ] [ j ] dp[k][i][j] dp[k][i][j]表示以 i i i为根的子树,在前 k k k个儿子中,分离一个大小为 j j j的子树的最少的操作次数。

d p [ k + 1 ] [ i ] [ j ] = m i n ( d p [ k ] [ i ] [ j − t ] + d p [ f u l l s o n [ v ] ] [ v ] [ t ] ) dp[k+1][i][j]=min(dp[k][i][j-t]+dp[full_son[v]][v][t]) dp[k+1][i][j]=min(dp[k][i][jt]+dp[fullson[v]][v][t])

$j=m->1 $

t = 1 − > j t=1->j t=1>j

$ dp[i][j]=min(dp[i][j-t]+dp[v][t]-2)$

初始化:

d p [ u ] [ 0 ] = 0 dp[u][0]=0 dp[u][0]=0

d p [ u ] [ 1 ] = d u [ u ] dp[u][1]=du[u] dp[u][1]=du[u] u u u相连的边数

减2的原因:我们通过v更新u,就要保留u->v 此条边,但是

想想我们的初始化,我们在 f [ u ] [ k ] f[u][k] f[u][k]中砍掉了这条边一次

又在 f [ v ] [ j − k ] f[v][j-k] f[v][jk]中砍掉了这条边一次,所以要加回来

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=201;
struct Edge{
    int to,next;
}e[N*2];
int du[N],a[N],dp[N][N];
int n,k,res=INF,EdgeCnt=0;
void addedge(int u,int v){
    int p=++EdgeCnt;
    e[p].to=v;e[p].next=a[u];
    a[u]=p;
}
void dfs(int u,int fa){
    dp[u][1]=du[u];
    for (int p=a[u];p;p=e[p].next){
        int v=e[p].to;
        if (v!=fa){
            dfs(v,u);
            for (int j=k;j>=1;j--)
                for (int k=1;k<=j;k++)
                    dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[v][k]-2);
        }
    }
    res=min(res,dp[u][k]);
}
int main(){
    scanf("%d%d",&n,&k);
    memset(dp,0x3f,sizeof(dp));
    for (int i=1;i<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        addedge(u,v);
        addedge(v,u);
        du[u]++;du[v]++;
    }
    dfs(1,0);
    printf("%d",res);
    return 0;
}

[P3177 HAOI2015]树上染色 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题意:一棵树上一共 n n n个点, k k k个点是黑色的, n − k n-k nk个点是白色的,问黑色点两两之间与白色点两两之间距离最大值之和。

考虑每一条边的贡献,如果两个同色的点在边的两侧,则贡献为 w w w,否则贡献为0。

s z [ ] sz[] sz[]维护子树中结点的数量

贡献为 t o t ∗ w tot*w totw

t o t = k ∗ ( m − k ) + ( s z [ p ] − k ) ∗ ( n − m − ( s z [ p ] − k ) ) tot=k*(m-k)+(sz[p]-k)*(n-m-(sz[p]-k)) tot=k(mk)+(sz[p]k)(nm(sz[p]k))

d p [ u ] [ k ] = m a x ( d p [ u ] [ k ] , d [ u ] [ j − k ] + d p [ p ] [ k ] + t o t ∗ w ) dp[u][k] = max(dp[u][k], d[u][j - k] + dp[p][k] + tot * w) dp[u][k]=max(dp[u][k],d[u][jk]+dp[p][k]+totw)

坑点好多!!!

j = m i n ( s z [ u ] , m ) − > 0 j = min(sz[u], m) ->0 j=min(sz[u],m)>0

k = m i n ( j , s z [ p ] ) − > 1 k=min(j,sz[p])->1 k=min(j,sz[p])>1

保证 u u u的子节点 p p p一定被选上

每次更新的时候 d p [ u ] [ k ] dp[u][k] dp[u][k]一定需要 d p [ u ] [ j − k ] dp[u][j-k] dp[u][jk]被更新才能被更新

总结:树形背包一般都需要维护 s z [ ] sz[] sz[]

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>

using namespace std;


typedef long long ll;
const int MAXN = 2020;
int n, m, id;
int head[MAXN], sz[MAXN];
ll f[MAXN][MAXN];

struct Edge{
    int to, next, w;
}E[MAXN * 2];

void addedge(int u, int v, int w)
{
    E[id].w = w;
    E[id].to = v;
    E[id].next = head[u];
    head[u] = id++;
}


void dfs(int u, int fa)
{
    sz[u] = 1;
    f[u][0] = f[u][1] = 0;
    for(int i = head[u]; i != -1; i = E[i].next)
    {
        int p = E[i].to, w = E[i].w;
        if(p == fa) continue;
        dfs(p, u);
        sz[u] += sz[p];
        for(int j = min(m, sz[u]); j >= 0; j--)
        {
            for(int k = 0; k <= min(sz[p], j); k++)
            {
                if(f[u][j - k] == -1) continue;
                ll tot = (ll)k * (m - k) + (ll)(sz[p] - k) * (n - m - (sz[p] - k));
                f[u][j] = max(f[u][j], f[u][j - k] + f[p][k] + (ll)tot * w);
            }
        }
    }
}

int main()
{

    memset(head, -1, sizeof(head));
    cin >> n >> m;
    if(n - m < m) m = n - m;
    for(int i = 1; i < n; i++)
    {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        addedge(u, v, w);
        addedge(v, u, w);
    }
    memset(f, -1, sizeof(f));
    dfs(1, -1);
    printf("%lld\n", f[1][m]);
    return 0;
}

[P3174 HAOI2009]毛毛虫 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题意:求一条链与其相连的边所覆盖的点的最大值

从根节点开始维护最长链与次长链,注意每一种情况的特判,注意分类讨论的严谨

实时更新 a n s ans ans

s z [ u ] sz[u] sz[u]维护以 u u u为根的最长毛毛虫数量

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int MAXN = 3e5 + 60;
int head[MAXN], sz[MAXN], d[MAXN];
int id, n, m, ans;
struct Edge{
    int to, next;
}E[MAXN * 2];

void addedge(int u, int v)
{
    E[id].to = v;
    E[id].next = head[u];
    head[u] = id++;
    return;
}

void dfs(int u, int fa)
{
    sz[u] = 0;
    int mx0 = 0, mx1 = 0;
    for(int i = head[u]; i != -1; i = E[i].next)
    {
        int p = E[i].to;
        if(p == fa) continue;
        dfs(p, u);
        sz[u] = max(sz[u], sz[p]);
        if(sz[p] >= mx0) {mx1 = mx0; mx0 = sz[p];}
        else if(sz[p] > mx1) mx1 = sz[p];
    }
    if(mx0 == 0) ans = max(ans, d[u] + 1);
    else if(mx1 == 0) ans = max(ans, mx0 + d[u]);
    else ans = max(ans, mx0 + mx1 + d[u] - 1);
    sz[u] += ((d[u] == 1) ? 1 : (d[u] - 1));
}

int main()
{
    memset(head, -1, sizeof(head));
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; i++)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        addedge(u, v);
        addedge(v, u);
        d[u] ++;
        d[v] ++;
    }
    dfs(1, -1);

    printf("%d\n", ans);

    return 0;
}

P6554 Promises I Can’t Keep - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)(换根DP)

第一遍dfs时的根为root, f u f_u fu表示路径和, w u w_u wu表示该点的权值, c n t u cnt_u cntu表示以 u u u为根的子树中叶子节点的数量.

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 1e6 + 5;

int head[N], ver[N], net[N], idx;
int cnt[N], dg[N], root;
bool is[N];
double sum[N], f[N], w[N];

void add(int a, int b)
{
    net[idx] = head[a];
    ver[idx] = b;
    head[a] = idx++;
}

void dfs1(int u, int fa)
{
    for (int i = head[u]; ~i; i = net[i])
    {
        int v = ver[i];
        if (v == fa)
            continue;
        dfs1(v, u);
        cnt[u] += cnt[v];
        sum[u] += sum[v] + w[u] * cnt[v];
    }    
    if (!cnt[u])
        sum[u] = w[u], cnt[u] = 1, is[u] = true;
}

void dfs2(int u, int fa)
{
    for (int i = head[u]; ~i; i = net[i])
    {
        int v = ver[i];
        if (v == fa)
            continue;
        if (is[v])
            f[v] = f[u] - w[u] + (cnt[root] - 2) * w[v];
        else
            f[v] = f[u] - w[u] * cnt[v] + (cnt[root] - cnt[v]) * w[v];
        dfs2(v, u);
    }
}

int main()
{
    memset(head, -1, sizeof(head));
    int n;
    scanf("%d", &n);
    for (int i = 1; i < n; i++)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        add(u, v), add(v, u);
        dg[u]++, dg[v]++;
    }
    for (int i = 1; i <= n; i++)
    {
        scanf("%lf", &w[i]);      
        if (dg[i] > 1)
            root = i;//找出根节点
    }
    dfs1(root, 0);
    f[root] = sum[root];
    dfs2(root, 0);
    double ans = -1e13;
    for (int i = 1; i <= n; i++)
        ans = max(ans, (double)f[i] / (cnt[root] - is[i]));//计算期望
    printf("%.4lf", ans);
    return 0;
}
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值