树形dp小结——1

本文总结了树形动态规划的基本概念和应用,包括自下而上和自上而下的策略。通过三个入门题目:上司的舞会、树的重心和树上最远距离,详细解析了树形dp的状态转移方程和解题思路,帮助读者理解和掌握这一技巧。

1.通常的动态规划都是线性的,或者说是在有向无环图上进行的。而树形dp就是在树的基础上进行的dp

2.树形dp通常有两种方向,一种是自下而上,另一种是自上而下。具体利用方法根据实际要求来

叶——根,在回溯的时候从叶子节点往上更新信息

根——叶,往往是在叶——根dfs一遍以后(相当于预处理),再重新往下获取,得到的最终答案。

3.和普通dp一样,树形dp的重点在于找状态转移方程。


一.入门题

上司的舞会上司的舞会

题目大意:

如题目所言,就是自己的上司参加的话,自己就不能参加。每个节点定义一个价值,子节点和父亲节点不能同时选取。求能够选取的最大价值

题目分析:令f【u】【0】表示不选取u节点,u子树所能得到的最大价值;f【u】【1】表示选取了u节点,u子树能得到的最大价值。

在回溯的过程中通过子节点向上更新信息

设i号节点的价值为a【i】,<u,v>是一条有向边

f【u】【0】=max(f【v】【0】,f【v】【1】)

f【u】【1】=a【u】+f【v】【0】u取了,v肯定不取

void dfs(int x)
{
    int l=tu[x].size();
    for(int i=0;i<l;i++)
    {
        int t=tu[x][i];
        dfs(t);
        dp[x][0]+=max(dp[t][1],dp[t][0]);
        dp[x][1]+=dp[t][0];
    }
}
dfs(root);
cout<<max(dp[root][0],dp[root][1])<<endl;

具体代码:

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<queue>
using namespace std;
typedef long long ll;
const int N=6010;
int m,n;
int dp[N][2],rd[N];
vector <int>tu[N];
void dfs(int x)
{
    int l=tu[x].size();
    for(int i=0;i<l;i++)
    {
        int t=tu[x][i];
        dfs(t);
        dp[x][0]+=max(dp[t][1],dp[t][0]);
        dp[x][1]+=dp[t][0];
    }
}

int main()
{
        while(~scanf("%d",&n))
       {
           for(int i=1;i<=n;i++)
        tu[i].clear();
        memset(rd,0,sizeof(rd));
       for(int i=1;i<=n;i++)
       {
           scanf("%d",&dp[i][1]);
           dp[i][0]=0;
       }
       int x,y;
       while(~scanf("%d%d",&x,&y)&&x+y)
           tu[y].push_back(x),rd[x]++;
        int ans=-1;
        for(int i=1;i<=n;i++)
            if(rd[i]==0)///防止不止一个根节点
            {dfs(i);ans=max(ans,max(dp[i][0],dp[i][1]));}
        cout<<ans<<endl;
       }
}


入门题二:

树的重心

题目:点击打开链接

题目大意:

若树上一个节点满足其所有子树中最大子树节点数最少,则这个点即为重心

题目分析:

一个节点有多支是儿子节点,而父亲节点只有一支,题目的意思很清晰,就是在所有儿子子树中寻求一个最大的,然后跟父亲节点的一支进行比较,实际上难在理解,此题将父亲节点的那支也定义成了节点u的子树之一了。而父亲节点的那一支就用总数n减去u的子树所有的节点个数。

任选一点为根,只要统计出每个点的子树大小,就能很快求出每个点子树节点的数量最大值

求每个点子树的大小同样只需要自底向上的更新信息。记size【u】为子树节点u节点的数量(包括u本身)

size【u】=1+(size【v】的总和)

void dfs(int father,int u,int n)
{
    int v,m,temp;
    for(int i=next[u];i!=-1;i=edge[i].next)
    {
        v=edge[i].v;
        if(v==father)
            continue;
        dfs(u,v,n);//先dfs再加
        sum[u]+=sum[v];
    }
  
    dp[u]=n-1-sum[u];
    for(int i=next[u];i!=-1;i=edge[i].next)
    {
        v=edge[i].v;
        if(v==father)
            continue;
        dp[u]=max(dp[u],sum[v]);
    }
    sum[u]++;
}

代码:

#include<iostream>
#include<string.h>
#include<algorithm>
#define MAX 22000
using namespace std;
struct edge
{
    int v;
    int next;
}edge[MAX*2];
int next[MAX];
int sum[MAX];
int dp[MAX];

int ans,n,num;
void add_edge(int u,int v)
{
    edge[num].next=next[u];
    next[u]=num;
    edge[num++].v=v;
}


void dfs(int father,int u,int n)
{
    int v,m,temp;
    for(int i=next[u];i!=-1;i=edge[i].next)
    {
        v=edge[i].v;
        if(v==father)
            continue;
        dfs(u,v,n);
        sum[u]+=sum[v];
    }
  //  cout<<u<<": "<<sum[u]<<endl;
    dp[u]=n-1-sum[u];
    for(int i=next[u];i!=-1;i=edge[i].next)
    {
        v=edge[i].v;
        if(v==father)
            continue;
        dp[u]=max(dp[u],sum[v]);
    }
    sum[u]++;
}
int main()
{
    int t;
    int p,q;
    cin>>t;
    while(t--)
    {
        cin>>n;
        num=0;
        memset(sum,0,sizeof(sum));
        memset(next,-1,sizeof(next));
        for(int i=0;i<n-1;i++)
        {
            cin>>p>>q;
            add_edge(p,q);
            add_edge(q,p);
        }
        ans=1<<29;
        p=0;
        dfs(0,1,n);
        for(int i=1;i<=n;i++)
        {
            if(dp[i]<ans)
            {
                ans=dp[i];
                p=i;
            }
        }
        cout<<p<<" "<<ans<<endl;
    }
    return 0;
}


入门题3:树上最远距离

这道题最好需要画图理解。

题目大意:如题目所述,一棵无向树,辺有权值,求树上每个点能到达的最远距离

题目分析:

一个点能够走最远距离,第一步要么往儿子方向走,要么找父亲节点。

如果往儿子结点走,就会一直往儿子结点走,不存在返回的现象(以后会遇到又返回的那种题)

如果第一步往父亲节点走,那么第二步要么继续往他的父亲节点走,要么走u的兄弟子树(注意是兄弟,因为不可能回来)兄弟中的最大距离就是次最大距离的含义

%%%往儿子方向走的最远距离是很容易求出来的,而往父亲节点走的就需要自上往下推。这道题与之前两道题是不一样的,就牵扯到了从根向下传递信息。

&&&记f【u】为节点u第一步想儿子方向走的最远距离

&&&记g【u】为节点u第一步向父亲方向走的最远距离

&&&记p【u】为节点u的父亲节点的编号

&&&f【u】=max{f【v】+w(u,v)}

&&&g【u】=w(u,p【u】,max{g【p【u】】,f【v】+w(p【u】,u)})v是u的兄弟

&&&所以要两边dfs,先求出f再求出g

&&&最后节点u的最远距离为  max{f【u】,g【u】}

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 10200
struct edge
{
    int to;//终端点
    int next;//下一条同样起点的边号
    int w;//权值
}bians[MAXN*2];
int tot;//总边数
int head[MAXN];//head[u]=i表示以u为起点的所有边中的第一条边是 i号边
void add_bian(int u,int v,int w)//添加从u->v,权值为w的边
{
    bians[tot].to=v;
    bians[tot].w=w;
    bians[tot].next = head[u];
    head[u] = tot++;
}
int dist[MAXN][3];//dist[i][0,1,2]分别为正向最大距离,正向次大距离,反向最大距离
int jilu[MAXN];
int dfs1(int u,int fa)//返回u的正向最大距离
{
    if(dist[u][0]>=0)
        return dist[u][0];
    dist[u][0]=dist[u][1]=dist[u][2]=jilu[u]=0;

    for(int e=head[u]; e!=-1; e=bians[e].next)
    {
        int v= bians[e].to;
        if(v==fa)
            continue;
        if(dist[u][0]<dfs1(v,u)+bians[e].w)
        {
            jilu[u]=v;
            dist[u][1] = max(dist[u][1] , dist[u][0]);
            dist[u][0]=dfs1(v,u)+bians[e].w;
        }
        else if( dist[u][1]< dfs1(v,u)+bians[e].w )
            dist[u][1] = max(dist[u][1] , dfs1(v,u)+bians[e].w);
    }
    return dist[u][0];
}
void dfs2(int u,int fa)
{
    for(int e=head[u];e!=-1;e=bians[e].next)
    {
        int v = bians[e].to;
        if(v==fa)
            continue;
        if(v==jilu[u])//以u为根节点的经过下一叶子的标号为v
        dist[v][2] = max(dist[u][2],dist[u][1])+bians[e].w;//如果是这样的话,那么就找down次最大值与up最大值的最大值
        else
        dist[v][2] = max(dist[u][2],dist[u][0])+bians[e].w;//如果没有前边的限制,则说明还没到最后,就找down与up哪种最大
        dfs2(v,u);
    }
}
int main()
{
    int n;
    while(scanf("%d",&n)==1&&n)
    {
        tot=0;
        memset(dist,-1,sizeof(dist));
        memset(head,-1,sizeof(head));
        memset(jilu,-1,sizeof(jilu));
        for(int i=2; i<=n; i++)
        {
            int v,w;
            scanf("%d%d",&v,&w);
            add_bian(i,v,w);//加边
            add_bian(v,i,w);//加边
        }
        dfs1(1,-1);
        dfs2(1,-1);
        for(int i=1;i<=n;i++)
            printf("%d\n",max(dist[i][0],dist[i][2]));
    }
    return 0;
}











【完美复现】面向配电网韧性提升的移动储能预布局与动态调度策略【IEEE33节点】(Matlab代码实现)内容概要:本文介绍了基于IEEE33节点的配电网韧性提升方法,重点研究了移动储能系统的预布局与动态调度策略。通过Matlab代码实现,提出了一种结合预配置和动态调度的两阶段优化模型,旨在应对电网故障或极端事件时快速恢复供电能力。文中采用了多种智能优化算法(如PSO、MPSO、TACPSO、SOA、GA等)进行对比分析,验证所提策略的有效性和优越性。研究不仅关注移动储能单元的初始部署位置,还深入探讨其在故障发生后的动态路径规划与电力支援过程,从而全面提升配电网的韧性水平。; 适合人群:具备电力系统基础知识和Matlab编程能力的研究生、科研人员及从事智能电网、能源系统优化等相关领域的工程技术人员。; 使用场景及目标:①用于科研复现,特别是IEEE顶刊或SCI一区论文中关于配电网韧性、应急电源调度的研究;②支撑电力系统在灾害或故障条件下的恢复力优化设计,提升实际电网应对突发事件的能力;③为移动储能系统在智能配电网中的应用提供理论依据和技术支持。; 阅读建议:建议读者结合提供的Matlab代码逐模块分析,重点关注目标函数建模、约束条件设置以及智能算法的实现细节。同时推荐参考文中提及的MPS预配置与动态调度上下两部分,系统掌握完整的技术路线,并可通过替换不同算法或测试系统进一步拓展研究。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值