系统性的学习下树形dp。。虽然时间不太够了。。但是搏一搏,单车变摩托的道理还是要信的。。。。之前的太零散了,感觉很弱鸡。。。。
1.Hdu 2196 Computer 网上流传很广的一道求树的直径的题,f[i][0]表示顶点为i的子树的,距顶点i的最长距离
f[i][1]表示i到不是i的子树的最大距离。两遍dfs,第一遍可以求出所有的f[i][0],第二遍dfs,当走到结点u时,找到结点u经过儿子结点v1的最大距离和经过儿子结点v2第二大距离,然后状态转移可以得到所有儿子结点的f[v][1]:
如果vi不是u最长距离经过的节点,f[vi][1] = dist(vi,u)+max(f[u][0], f[u][1])
如果vi是u最长距离经过的节点,那么不能选择f[u][0],因为这保存的就是最长距离,要选择Tree(u)第二大距离secondDist,
可得f[vi][1] = dist(vi, u) + max(secondDist, f[u][1]);
(以上画个图即可明白)。
2.poj1155 TELE 树上的背包问题
题意:给定一棵树,1为根结点表示电视台,有m个叶子节点表示客户,有n-m-1个中间节点表示中转站,每条树边有权值。现在要在电视台播放一场比赛,每个客户愿意花费cost[i]的钱观看,而从电视台到每个客户也都有个费用,并且经过一条边只会产生一个费用。问电视台不亏损的情况最多有几个客户可以看到比赛?1<=n<=1000,1<=m<=n-1;
dp[i][j]表示以结点i为根得到j个用户时得到的最大价值,
void dfs(int u,int fa)
{
for(Edge* it=adj[u];it;it=it->nxt)
{
int v=it->v,w=it->w;
if(v==fa) continue;
dfs(v,u);
num[u]+=num[v];
for(int i=num[u];i>=0;i--)
for(int j=0;j<=i;j++)
if(dp[u][i-j]!=-INF&&dp[v][j]!=-INF)
dp[u][i]=max(dp[u][i],dp[u][i-j]+dp[v][j]-w);
}
}
最后统计dp[1][j]中最大的j使得dp[1][j]>=0即可。这个j为答案。
3.information disturbing hdu 3586 二分枚举切边得到最小的最大切边使得所有叶子节点与根节点不连通。dp[i]表示i节点时所求得所有切边和。
/*
* Author: ktmzgl
* Created Time: 2016/9/28 18:42:32
* File Name: F:\Vim\code\9.28_hdu_3586.cpp
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <time.h>
using namespace std;
const int maxint = 10000000;
const int maxn=1010;
int n,m;
int x,y,w;
int tot;
int head[maxn];
int dp[maxn];
int maxw,minw;
struct Node
{
int v,w;
int next;
};
struct Node e[maxn*2+10];
void AddNode(int from, int to,int w)
{
e[tot].v=to;
e[tot].w=w;
e[tot].next=head[from];
head[from]=tot++;
}
void dfs(int p,int u,int lim)
{
for(int k=head[u]; k!=-1; k=e[k].next)
{
int v=e[k].v,w=e[k].w;
if(v==p) continue;
dfs(u,v,lim);
if(dp[v]==0)
{
if(lim<w)
{
dp[u]=maxint;
return;
}
else
dp[u]+=w;
}
else
{
if(lim<w)
{
dp[u]+=dp[v];
}
else
{
dp[u]+=min(dp[v],w);
}
}
}
}
void work()
{
int ans=1010;
int l=minw,r=maxw;
while(l<=r)
{
int mid=(l+r)/2;
memset(dp,0,sizeof(dp));
dfs(-1,1,mid);
if(dp[1]<=m)
{
r=mid-1;
ans=min(ans,mid);
}
else
l=mid+1;
}
/*for(int i=l;i<=r;i++)
{
memset(dp,0,sizeof(dp));
dfs(-1,1,i);
cout<<dp[1]<<endl;
}*/
if(ans==1010) ans=-1;
cout<<ans<<endl;
}
int main()
{
while(scanf("%d%d",&n,&m)&&!(n==0&&m==0))
{
if(n<=1)
{
cout<<0<<endl;
continue;
}
maxw=-1,minw=1010;
memset(head,-1,sizeof(head));
tot=0;
for(int i=0;i<n-1;i++)
{
scanf("%d%d%d",&x,&y,&w);
AddNode(x,y,w);
AddNode(y,x,w);
maxw=max(maxw,w);
minw=min(minw,w);
}
work();
}
}
poj 1947 rebuilding roads 树形dp+背包,这一题和第一题很类似,这种题目前遇到的是需要考虑去选子树最优值的时候去重(一般是滚动数组思想)。
题意:一棵节点数为n的树,问从这棵树最少删除几条边使得某棵子树的节点个数为p。
dp[i][j]表示结点为i的点为根时其子树节点(包括该点)为j时删边最小值。初始化dp为INF
转移:1.dp[i][1]=i的儿子节点个数
2.dp[i][j]=min(dp[i][j],dp[v][k]+dp[i][j-k]) |v为i的儿子结点
poj 3162 Walking Race 题解
题意:一棵树,求出从每个结点出发能到走的最长距离(每个结点最多只能经过一次),将这些距离按排成一个数组得到d[1],d[2],d[3]……d[n] ,在数列的d中求一个最长的区间,使得区间中的最大值与最小值的差不超过m。
这题正解应该是树的直径加单调队列(可以点这看单调队列),因为只用保存最大值最小值。
但是觉得onlogn的也能过,所以用了直径加rmq,但是时间是可以但是爆内存。。尴尬。。。贴上rmq(比较好理解,可以改成线段树能过)和单调队列。