树形DP就是在数据树上进行状态转移的算法。一般这个状态转移数组会存在三个维度:当前是index号节点为根节点的子树,已经选择了N个节点,这个节点选不选(或者其他)。
一般套路有:
- 链式前向星或者向量数组建树
- 在树上进行DFS
例题:
P1352 没有上司的舞会
本题就是一个很典型的树状结构上跑DP的例子。由题意可知:对于每一个节点的状态都需要知道它当前的编号和当前节点有没有被选上,也就是二维的状态转移。再由题目中讲述的选不选本节点会造成什么影响可以推出这样一组状态转移方程:
进行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过程中更新答案。
状态转移方程:因为剪断一枝花之后下面的部分就再也不会有联系了,而且美丽值需要逐项累加,所以状态转移方程为:
//#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:
大概是一段区间内的动态规划问题。
普遍套路有:(环形区间跑两遍)
- 枚举所求区间长度
- 枚举区间分割线
P1880 石子合并
这是一个环形区间,所以在最后一个元素与起始元素之间用一般套路解决问题可能会有断层。于是把数据扩展到2*n长度,对2*n长度的区间进行枚举就可以确保正确性。
整体区间的解肯定是局部最优解的总集,所以可以对此枚举各个长度的区间来一一判断。问题是一个子区间内仍然存在合并的先后问题,所以还需要一个分割线来再度分化这个子问题。
在分割线的作用下,就可以得到这样的状态转移方程。
也就是在一个区间内凭多种分割方案确定一个最大值。
//#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;
}