树形DP初步与区间DP初步(不定更)

树形DP涉及在数据树上进行状态转移,通常包括三个维度:节点、已选择节点数、节点选不选。文章通过P1352和P1122两道例题介绍了树形DP的应用,解释了如何进行DFS求解。区间DP处理区间内的动态规划问题,可通过枚举区间长度和分割线来解决,如在P1880石子合并问题中,采用扩展区间避免断层并找到最大值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

树形DP就是在数据树上进行状态转移的算法。一般这个状态转移数组会存在三个维度:当前是index号节点为根节点的子树,已经选择了N个节点,这个节点选不选(或者其他)。

一般套路有:

  1. 链式前向星或者向量数组建树
  2. 在树上进行DFS

例题:

P1352 没有上司的舞会

本题就是一个很典型的树状结构上跑DP的例子。由题意可知:对于每一个节点的状态都需要知道它当前的编号和当前节点有没有被选上,也就是二维的状态转移。再由题目中讲述的选不选本节点会造成什么影响可以推出这样一组状态转移方程:

dp(current)(0)=max(dp[nxt][1],dp[nxt][0])

dp[current][1]=dp[next][0]

进行DFS求解即可。

//#include<pch.h>
#include <iostream>
#include <cstdio>
#include <bits/stdc++.h>
#include<queue>
#include <map>
#include <algorithm>
#include <stack>
#include <iomanip>
#include <cstring>
#include <cmath>
#define DETERMINATION main
#define lldin(a) scanf_s("%lld", &a)
#define println(a) printf("%lld\n", a)
#define reset(a, b) memset(a, b, sizeof(a))
#define debug cout<<"procedures above are available"<<endl;
using namespace std;
const int INF = 0x3f3f3f3f;
const double PI = acos(-1);
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int mod = 1000000007;
const int tool_const = 19991126;
const int tool_const2 = 33;
inline ll nextLong()
{
    ll tmp = 0, si = 1;
    char c;
    c = getchar();
    while (c > '9' || c < '0')
    {
        if (c == '-')
            si = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
    {
        tmp = tmp * 10 + c - '0';
        c = getchar();
    }
    return si * tmp;
}
/**Maintain your determination.Nobody knows the magnificent landscape
at his destination before the arrival with stumble.**/
/**Last Remote**/
ll happiness[500000];
vector<ll>leadership[50000];/建图
bool worship[500000];
ll status[500000][3];
void seek(ll current)//DFS
{
    status[current][0]=0;//没来就不赋值
    status[current][1]=happiness[current];
    for(int i=0;i<leadership[current].size();i++)
    {
        ll nxt=leadership[current][i];
        seek(nxt);
        status[current][0]+=max(status[nxt][0],status[nxt][1]);//两种状态
        status[current][1]+=status[nxt][0];
    }
}
int DETERMINATION()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    ll n;
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>happiness[i];
    ll employee,employer;
    for(int i=1;i<=n;i++)
    {
        cin>>employee>>employer;
        if(employer==0&&employee==0)
            continue;
        leadership[employer].push_back(employee);//建图
        worship[employee]=1;//找根节点,也就是最大的BOSS
    }
    ll headmaster=0;
    for(int i=1;i<=n;i++)
        if(!worship[i])
        {
            headmaster=i;
            break;
        }
    //cout<<headmaster<<endl;
    seek(headmaster);
    cout<<max(status[headmaster][1],status[headmaster][0])<<endl;
    return 0;
}

P1122 最大子树和

本题存在的一个难题是根节点不明确,所以这就要在DFS过程中更新答案。

状态转移方程:因为剪断一枝花之后下面的部分就再也不会有联系了,而且美丽值需要逐项累加,所以状态转移方程为:

dp[current][1]+=max(dp[nxt][1],0)

//#include<pch.h>
#include <iostream>
#include <cstdio>
#include <bits/stdc++.h>
#include<queue>
#include <map>
#include <algorithm>
#include <stack>
#include <iomanip>
#include <cstring>
#include <cmath>
#define DETERMINATION main
#define lldin(a) scanf_s("%lld", &a)
#define println(a) printf("%lld\n", a)
#define reset(a, b) memset(a, b, sizeof(a))
#define debug cout<<"procedures above are available"<<endl;
using namespace std;
const int INF = 0x3f3f3f3f;
const double PI = acos(-1);
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int mod = 1000000007;
const int tool_const = 19991126;
const int tool_const2 = 33;
inline ll nextLong()
{
    ll tmp = 0, si = 1;
    char c;
    c = getchar();
    while (c > '9' || c < '0')
    {
        if (c == '-')
            si = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
    {
        tmp = tmp * 10 + c - '0';
        c = getchar();
    }
    return si * tmp;
}
/**Maintain your determination.Nobody knows the magnificent landscape
at his destination before the arrival with stumble.**/
/**Last Remote**/
ll beau[500000],status[500000][3];
ll vis[150000],cnt=1;
struct edge
{
    ll next,to;
}margin[500000];
ll heads[500000];
void cons(ll from,ll to)
{
    margin[cnt]=edge{heads[from],to};
    heads[from]=cnt++;
}
ll mx=-1;
void seek(ll current,ll previous)
{
    status[current][0]=0;//剪断了就不可能有值了
    status[current][1]=beau[current];
    for(int i=heads[current];i!=-1;i=margin[i].next)
    {
        ll nxt=margin[i].to;
        if(nxt!=previous)
        {
            seek(nxt,current);
            status[current][1]+=max(ll(0),status[nxt][1]);//转移方程
        }
    }
    mx=max(mx,status[current][1]);//记录答案
}
int DETERMINATION()
{
    //ios::sync_with_stdio(false);
    //cin.tie(0),cout.tie(0);
    ll n;
    n=nextLong();
    reset(heads,-1);
    //ll totality=0;
    for(int i=1;i<=n;i++)
    {
        beau[i]=nextLong();
        //totality+=beau[i];
        if(beau[i]<0)
            vis[i]=-INF;
    }
    ll tmp1,tmp2;
    for(int i=1;i<=n-1;i++)
    {
        tmp1=nextLong(),tmp2=nextLong();
        cons(tmp1,tmp2);
        cons(tmp2,tmp1);
    }
    seek(1,1);
    println(mx);
    return 0;
}

区间DP:

大概是一段区间内的动态规划问题。

普遍套路有:(环形区间跑两遍)

  1. 枚举所求区间长度
  2. 枚举区间分割线

P1880 石子合并

 这是一个环形区间,所以在最后一个元素与起始元素之间用一般套路解决问题可能会有断层。于是把数据扩展到2*n长度,对2*n长度的区间进行枚举就可以确保正确性。

整体区间的解肯定是局部最优解的总集,所以可以对此枚举各个长度的区间来一一判断。问题是一个子区间内仍然存在合并的先后问题,所以还需要一个分割线来再度分化这个子问题。

在分割线的作用下,就可以得到这样的状态转移方程。

dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1])

也就是在一个区间内凭多种分割方案确定一个最大值。

//#include<pch.h>
#include <iostream>
#include <cstdio>
#include <bits/stdc++.h>
#include<queue>
#include <map>
#include <algorithm>
#include <stack>
#include <iomanip>
#include <cstring>
#include <cmath>
#define DETERMINATION main
#define lldin(a) scanf_s("%lld", &a)
#define println(a) printf("%lld\n", a)
#define print(a) printf("%lld ", a)
#define reset(a, b) memset(a, b, sizeof(a))
#define debug cout<<"procedures above are available"<<endl;
using namespace std;
const int INF = 0x3f3f3f3f;
const double PI = acos(-1);
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int mod = 1000000007;
const int tool_const = 19991126;
const int tool_const2 = 33;
inline ll nextLong()
{
    ll tmp = 0, si = 1;
    char c;
    c = getchar();
    while (c > '9' || c < '0')
    {
        if (c == '-')
            si = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
    {
        tmp = tmp * 10 + c - '0';
        c = getchar();
    }
    return si * tmp;
}
/**Maintain your determination.Nobody knows the magnificent landscape
at his destination before the arrival with stumble.**/
/**Last Remote**/
ll sum[900],num[900],max_status[500][500],min_status[500][500];
int DETERMINATION()
{
    ll n;
    n=nextLong();
    for(int i=1;i<=n;i++)
    {
        num[i]=nextLong();
        num[i+n]=num[i];
    }
    for(int i=1;i<=2*n;i++)
        sum[i]=sum[i-1]+num[i];
    //cout<<sum[5]-sum[3]<<endl;
    for(int dis=1;dis<n;dis++)
    {
        for(int pt1=1,pt2=pt1+dis;pt1<2*n&&pt2<2*n;pt1++,pt2=pt1+dis)
        {
            min_status[pt1][pt2]=INF;
            for(int i=pt1;i<pt2;i++)
            {
                min_status[pt1][pt2]=min(min_status[pt1][pt2],
                min_status[pt1][i]+min_status[i+1][pt2]+sum[pt2]-sum[pt1-1]);
                max_status[pt1][pt2]=max(max_status[pt1][pt2],
                max_status[pt1][i]+max_status[i+1][pt2]+sum[pt2]-sum[pt1-1]);
            }
        }
    }
    ll mx=-1,mn=INF;
    for(int i=1;i<n;i++)
    {
        mx=max(mx,max_status[i][i+n-1]);
        mn=min(mn,min_status[i][i+n-1]);
    }
    println(mn),println(mx);
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值