说实话,树形DP本不应该单独拿出来说,因为它本质就是一个动态规划。而且与一般的树形结构不同,树形DP不会去用临接表建图。这可能也是树形DP没有一个比较基础的教程的原因。
一道入门题也许可以让自己对树形dp有一个粗略的认识
题目描述
某大学有N个职员,编号为1~N。他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数Ri,但是呢,如果某个职员的上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。
输入输出格式
输入格式:
第一行一个整数N。(1<=N<=6000)
接下来N行,第i+1行表示i号职员的快乐指数Ri。(-128<=Ri<=127)
接下来N-1行,每行输入一对整数L,K。表示K是L的直接上司。
最后一行输入0 0
输出格式:
输出最大的快乐指数。
输入输出样例
输入样例:
7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5
0 0
输出样例:
5
题中很明确的告知,学校里的职员关系像一颗树一样,以校长为根
于是,抽象出模型,就是一颗每个点都有权值的树
然后,题中给了一个条件,若某个节点u被选用,其子节点v都不可被选用
题中的问题是,要在满足上述条件的要求下,得到一个最大权值
由于每个节点只有选和不选两种状态,于是设f[u][1]指v号节点被选用后的最大值,f[u][0]是指v号节点没被选用时的最大值
对于选u号节点来说,u号节点的所有直接子节点v都不能选
对于不选u号节点来说,他的子节点v可选可不选
此时不难得到状态转移方程如下
f [ u ] [ 1 ] = s u m ( f [ v ] [ 0 ] ) + h a p p y [ u ] f[u][1]=sum(f[v][0])+happy[u] f[u][1]=sum(f[v][0])+happy[u]
f [ u ] [ 0 ] = s u m ( m a x ( f [ v ] [ 0 ] , f [ v ] [ 1 ] ) ) f[u][0]=sum(max(f[v][0],f[v][1])) f[u][0]=sum(max(f[v][0],f[v][1]))
分析状态转移方程可知,要知道节点u的状态,必须要知道其子节点v的状态,于是我们就有两种实现方法:从底层循环或直接dfs,这两种实现方法中dfs可能比较好打,但从底层循环不会有爆栈的风险
#include
using namespace std;
#define frein(txtname); freopen(txtname,"r",stdin);
#define loop(i,start,end) for(int i=start;i<=end;i++)
#define clean(arry,num); memset(arry,num,sizeof(arry));
#define min(a,b) ((a) < (b) ? (a) : (b))
#define max(a,b) ((a) > (b) ? (a) : (b))
int n,master;
const int maxn=6000+10;
int happy[maxn];
vector<int>follow[maxn];
bool vis[maxn];
int dp[maxn][2];
inline int read()
{
int ans=0;bool negive=false;char r=getchar();
while(r<'0'||r>'9'){if(r=='-')negive=true;r=getchar();}
while(r>='0'&&r<='9'){ans=ans*10+r-'0';r=getchar();}
return (negive)?-ans:ans;
}
void datasetting()
{
n=read();
loop(i,1,n)happy[i]=read();
clean(vis,false);
int k,l;l=read();k=read();
while(l*k)
{
follow[k].push_back(l);
vis[l]=true;
l=read();k=read();
}
loop(i,1,n)if(!vis[i]){master=i;break;}
}
void dfs(int x)
{
dp[x][0]=0;
dp[x][1]=happy[x];
int len=follow[x].size();
loop(i,0,len-1)
{
int son=follow[x][i];
dfs(son);
dp[x][0]+=max(dp[son][1],dp[son][0]);
dp[x][1]+=dp[son][0];
}
}
int main()
{
frein("datain.txt");
datasetting();
dfs(master);
printf("%d",max(dp[master][0],dp[master][1]));
return 0;
}
/********************************************************************
ID:Andrew_82
LANG:C++
PROG:Tree shape dynamic programming
********************************************************************/
鄙人知识浅薄,望各位大佬斧正
update 2019.1.19
great!
再来几道题吧
时间限制: 1 Sec 内存限制: 128 MB
题目描述
Bob特别喜欢战略游戏,但有时他不能尽快找到最优解,所以他就很伤心。现在他又有一个问题,他必须保卫一个中世纪的城市,这个城市的道路形成了一棵树。他需要在树的节点上放最少的士兵来观察所有的边。你能帮助他么?
例如下图就只需要一个士兵放在1号节点。
输入文件soldier.in中有多组数据,每组数据的第一行N表示点的个数。接下来N行每行格式如下
x:(k) a1 a2 … ak(x为点的编号,k为与其相连的子节点个数,a1, a2, …, ak分别为子节点的编号)
输出
输出文件soldier.out,对于每组数据输出一行一个数,即最少士兵数。
样例输入
4
0:(1) 1
1:(2) 2 3
2:(0)
3:(0)
5
3:(3) 1 4 2
1:(1) 0
2:(0)
0:(0)
4:(0)
样例输出
1
2
#include
using namespace std;
#define loop(i,start,end) for(int i=start;i<=end;i++)
#define clean(arry,num); memset(arry,num,sizeof(arry));
#define min(a,b) (a>b)?b:a
const int maxn=2000;
class treeDP
{
private:
int dp[maxn][2];
int n;
vector<int>edge[maxn];
inline void addl(int u,int v);
void calc(int u,int fa);
public:
void work();
}fun;
inline void treeDP::addl(int u,int v){edge[u].push_back(v);}
void treeDP::calc(int u,int fa)
{
dp[u][0]=0;dp[u][1]=1;
int len=edge[u].size();
loop(i,0,len-1)
{
int v=edge[u][i];
if(v==fa)continue;
calc(v,u);
dp[u][0]+=dp[v][1];
dp[u][1]+=min(dp[v][0],dp[v][1]);
}
}
void treeDP::work()
{
freopen("datain.txt","r",stdin);
while(scanf("%d",&fun.n)!=EOF)
{
loop(i,0,fun.n-1)fun.edge[i].clear();
loop(i,1,fun.n)
{
int x,y,z;scanf("%d:(%d)",&x,&y);
loop(j,1,y){scanf("%d",&z);fun.addl(x,z);fun.addl(z,x);}
}
fun.calc(0,-1);
printf("%d\n",min(fun.dp[0][0],fun.dp[0][1]));
}
return;
}
int main()
{
fun.work();
return 0;
}
/**************************************************************
Problem: DP
User: Andrew82
Language: C++
****************************************************************/
题目传送门
题目描述
乌托邦有n个城市,某些城市之间有公路连接。任意两个城市都可以通过公路直接或者间接到达,并且任意两个城市之间有且仅有一条路径(What does this imply? A tree!)。
每条公路都有自己的长度,这些长度都是已经测量好的。
小修想从一个城市出发开车到另一个城市,并且她希望经过的公路总长度最长。请问她应该选择哪两个城市?这个最长的长度是多少?
Input format:
第一行n(n<=1000)。
以下n-1行每行三个整数a, b, c。表示城市a和城市b之间有公路直接连接,并且公路的长度是c(c<=10000)。
Output format:
仅一个数,即最长长度。
Sample:
longest.in
5
1 2 2
2 3 1
2 4 3
1 5 4
Longest.out
9
说明:从城市4到城市5,经过的路径是4-2-1-5,总长度是9。
#include
using namespace std;
#define loop(i,start,end) for(int i=start;i<=end;i++)
#define clean(arry,num); memset(arry,num,sizeof(arry));
const int maxn=1010;
class DP
{
public:
struct node
{
int e;
int dis;
int nxt;
}edge[maxn<<1];
int head[maxn];
int f[maxn][2];
int n,cnt=0,ans=0;
inline int read()
{
int ans=0;char r=getchar();
while(r>'9'||r<'0')r=getchar();
while(r<='9'&&r>='0')
{ans=ans*10+r-'0';r=getchar();}
return ans;
}
inline void addl(int u,int v,int w)
{
edge[cnt].dis=w;
edge[cnt].e=v;
edge[cnt].nxt=head[u];
head[u]=cnt++;
}
void datasetting()
{
clean(head,-1);
n=read();
loop(i,1,n-1)
{
int a=read();int b=read();int c=read();
addl(a,b,c);
addl(b,a,c);
}
//clean(f,0);
}
void calc(int u,int fa)
{
f[u][0]=0;f[u][1]=0;
for(int i=head[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].e;int w=edge[i].dis;
if(v==fa)continue;
//printf("%d-->%d\n",u,v);
calc(v,u);
int dis=f[v][0]+w;
if(dis>f[u][0])
{
f[u][1]=f[u][0];
f[u][0]=dis;
}
else f[u][1]=(dis>f[u][1])?dis:f[u][1];
ans=(ans<f[u][1]+f[u][0])?f[u][1]+f[u][0]:ans;
}
return;
}
int Main()
{
datasetting();
calc(1,0);
printf("%d",ans);
}
}fun;
int main()
{
fun.Main();
return 0;
}
/**************************************************************
Problem: DP
User: mzg1802
Language:C++
****************************************************************/
题目传送门