树形dp
树是一种特别特别优美的结构
在树上做dp更是一种锦上添花的效果,虽然不知道是谁想出的操作,但是不得不佩服人家的脑力。但是,相对的说,树形dp对于刚接触就有一些措手不及:刚刚才从线性dp过来,树形dp就相当于一记闷棍,重重的敲打在我们的头上。高手勿谈

树形dp的关键
说到树形dp,存储首先是一项及其严肃的事情
许多刚接触树形dp的选手,上来就是一通使用链式前向星,邻接链表(很少用,TLE),邻接表等操作直接储存,以至于许多不熟悉图论的选手直接一脸mobile,(可能单纯只是对于我来说)
所以我觉得有必要说明一下怎样储存树形结构
1.邻接矩阵
Linus之父说过:"talk is cheap, show me the code"
我也会贯彻这一做法!(偷懒)
在代码中我做了相关的解释,我相信大家可以看的明白
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1000;
int weight[N][N];//weight[i][j]表示从节点i到节点j的权值为weight[i][j]
int visited[N];//防止重复访问
int n;//表示邻接矩阵的行列数
void dfs(int u){
visited[u]=1;
for(int i=1;i<=n;i++){
if(weight[u][i]){
//相关操作
cout<<u<<"->"<<i<<" "<<weight[u][i]<<endl;
if(visited[i]) continue;
dfs(i);
}
}
}
int main(){
int m;//n个节点和m条边
cin>>n>>m;
for(int i=1;i<=m;i++){
int a,b,c;
cin>>a>>b>>c;//表示节点a和节点b
weight[a][b]=c;//c表示权值
}
dfs(1);
system("pause");
return 0;
}
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int M=100;
const int N=100;
struct edge{
int u,v,weight;//u->v的权值为weight
}edge[M];
int n,m;//节点数和边数
int visited[N];//防止循环访问
void dfs(int u){
visited[u]=true;
for(int i=1;i<=m;i++){
if(edge[i].u==u){
int v=edge[i].v;
int weight=edge[i].weight;
//处理
cout<<u<<"->"<<v<<" "<<weight<<endl;
if(visited[v]) continue;
dfs(v);
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++){
int a,b,c;
cin>>a>>b>>c;
// edge[i].u=a;
// edge[i].v=b;
// edge[i].weight=c;
edge[i]={a,b,c};
//如果是双向边
//edge[i]={b,a,c};
}
dfs(1);
system("pause");
return 0;
}
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m;//节点的数目和边的数目
const int N=100;
struct edge{
int v;//终点
int weight;//权值
};
vector<edge> e[N];
void dfs(int u,int father){
for(auto ed:e[u]){
int v=ed.v;
int weight=ed.weight;
if(v==father) continue;//如果终点等于父节点,不用dfs,作用相当于visited数组
cout<<u<<"->"<<v<<" "<<weight<<endl;
dfs(v,u);
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++){
int a,b,c;
cin>>a>>b>>c;
e[a].push_back({b,c});
e[b].push_back({a,c});
}
dfs(1,0);
system("pause");
return 0;
}
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=100;
struct edge{
int u,v,weight;//起点和终点和权值
};
vector<edge> e;//边集
vector<int> head[N];//起点u的所有出边
void add(int a,int b,int c){
e.push_back({a,b,c});
head[a].push_back(e.size()-1);
}
void dfs(int u,int father){
for(int i=0;i<head[u].size();i++){
int j=head[u][i];//出边编号
int v=e[j].v;
int weight=e[j].weight;
if(v==father) continue;//终点为father,不会dfs
cout<<u<<"->"<<v<<" "<<weight<<endl;
dfs(v,u);
}
}
int main()
{
int n,m;//节点数目和边数目
cin>>n>>m;
for(int i=1;i<=m;i++){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
}
dfs(1,0);
system("pause");
return 0;
}
点击查看代码
#include<bits/stdc++.h>
using namespace std;
struct edge{
int v,weight,next;//终点,权值和下一条边
};
const int N=100,M=100;
edge e[M];
int index=0;
int head[N];//节点的第一条出边
void add(int a,int b,int c){
e[index]={b,c,head[a]};
head[a]=index++;
}
void dfs(int u,int father){
for(int i=head[u];~i;i=e[i].next){
int v=e[i].v;
int weight=e[i].weight;
if(v==father) continue;
cout<<u<<"->"<<v<<" "<<weight<<endl;
dfs(v,u);
}
}
int main()
{
int n,m;
cin>>n>>m;
memset(head,-1,sizeof head);
for(int i=1;i<=m;i++){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
}
dfs(1,0);
system("pause");
return 0;
}
例题
https://www.luogu.com.cn/problem/P2015
对于这道二叉苹果树来说简直就是树形dp的快乐题(Maybe)
直接上AC代码:
点击查看代码
/*
*/
/*
对题目的理解:
树形dp
*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
//读入优化
inline ll read()
{
ll a=0;
int f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-'){
f=-1;
ch=getchar();
}
}
while(isdigit(ch)){
a=(a<<3)+(a<<1)+ch-'0';
ch=getchar();
}
return a*f;
}
struct edge{//链式前向星去保存树
int v;//终点
int weight;//权值,在本题中相当于树枝上面的苹果数
int next;
};
const int maxn=105;
edge e[maxn<<1];
int index=0;
int head[maxn];
void add(int a,int b,int c){
e[index]={b,c,head[a]};
head[a]=index++;
}
int n,m;
int sz[maxn];
int dp[maxn][maxn];
void dfs(int u,int father){
for(int i=head[u];~i;i=e[i].next){
int v=e[i].v;
if(v==father) continue;
dfs(v,u);
sz[u]+=sz[v]+1;
for(int j=min(sz[u],m);j;j--){
for(int k=min(j-1,sz[v]);k>=0;k--){
dp[u][j]=max(dp[u][j],dp[u][j-k-1]+dp[v][k]+e[i].weight);
}
}
}
}
int main(){
memset(head,-1,sizeof head);
n=read();
m=read();
for(int i=1;i<n;i++){
int u=read(),v=read(),w=read();
add(u,v,w);
add(v,u,w);
}
dfs(1,0);
cout<<dp[1][m]<<endl;
system("pause");
return 0;
}
下面这道题也是一道充满快乐的题。
没有上司的舞会
就给大家留下练手了。网上也该有很多相关的习题,请大家慢慢品鉴!
后序我也会给大家更新一些我对数论的一些想法,敬请期待!
这才是和数学结合的最好方式

浙公网安备 33010602011771号