树形dp专辑

时间:2023-03-09 03:19:18
树形dp专辑

hdu 2196 http://acm.hdu.edu.cn/showproblem.php?pid=2196

input

5//5个结点

1 1//表示结点2到结点1有一条权值为1的边

2 1//表示结点3到结点2有一条权值为1的边

3 1

1 1

要我们求从任意结点出发的最长路径。

思路:一棵树上从某个结点出发的最长路径,肯定是①从该结点出发经由子树到大叶子结点。②从该结点出发经由父结点,然后到大叶子结点。      这两者中的最大者

  ①可以用一遍dfs求出任意结点到叶子结点的最大值。

  ②也是用一遍dfs求出任意结点经由父结点到达叶子结点的最大值

  最后两者取max即可

 #include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#include <iostream>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <string>
#include <math.h>
using namespace std;
typedef long long LL;
const int INF = <<;
const int N = + ;
struct computer
{
int v,len;
};
vector<computer> g[N];
int maxn[N],smax[N];
int maxnId[N],smaxId[N];
int up[N];//up[]记录的是从父亲结点转到根结点的最大值
//dfs生成一棵树,然后求的每个结点到叶子结点的最大值和次大值, 最大值和次大值不在一个分支上面
void dfs1(int u, int fa)
{
maxn[u] = smax[u] = ;
for(int i=; i<g[u].size(); ++i)
{
int v = g[u][i].v;
if(v!=fa)//如果u的一个分支v用来更新最长路,那么就不会更新次长路,所以maxn[u]是u最长的一个分支,smax[v]是v次长的一个分支
//因为是树,所以分支是没有交点的。
//感觉求出来的次长路不是次长路
{
dfs1(v,u);
if(maxn[u] < maxn[v] + g[u][i].len)//最长
{
smax[u] = maxn[u];
maxn[u] = maxn[v] + g[u][i].len;
smaxId[u] = maxnId[u];
maxnId[u] = v;//结点u的最长路是经过结点v这个分支然后到达叶子结点
}
else if(smax[u] < maxn[v] + g[u][i].len)//次长
{
smax[u] = maxn[v] + g[u][i].len;
smaxId[u] = v;
}
}
}
} void dfs2(int u, int fa)
{
for(int i=; i<g[u].size();++i)
{
int v = g[u][i].v;
if(v==fa) continue;
if(v!=maxnId[u])//如果父节点到根结点的最大值不是从v这个分支接入的,那么v从到结点再到根结点的最大值为
{
up[v] = max(up[u],maxn[u])+g[u][i].len;//v到父亲,然后父亲到叶子结点的最大值或者父亲经由父亲的父亲到达叶子结点的最大值
}
else//如果父节点到根结点的最大是从v这个分支接入的,那么fmax[u]+g[u][i].len就会有重叠的部分,所以只能用smax[u]+g[u][i].len
{
up[v] = max(up[u],smax[u]) + g[u][i].len;
}
dfs2(v,u);
}
}
int main()
{
int n,i,x,len;
computer tmp;
while(scanf("%d",&n)!=EOF)
{ for(i=; i<=n; ++i)
g[i].clear();
for(i=; i<=n; ++i)
{
scanf("%d%d",&x,&len);
tmp.v = x;
tmp.len = len;
g[i].push_back(tmp);
tmp.v = i;
g[x].push_back(tmp);
}
dfs1(,-);//设1为根结点,然后dfs,生成dfs树
up[] = ;//更结点没有父亲结点,所以经由父亲结点到达根结点的最大值为0
dfs2(,-); for(i=; i<=n; ++i)
printf("%d\n",max(maxn[i],up[i]));
}
return ;
}

 删点删边类

hdu3586 http://acm.hdu.edu.cn/showproblem.php?pid=3586

给定n和m

然后给定n-1条边和权值

要求删除边使得所有的叶子结点与根结点不连通,删边的花费为边的权值

要求使得花费总和小于m,且所有删边花费中的最大值最小。

思路:dp[u]是使得所有的叶子结点与结点u不连通的最小花费,  dp[u] += min(dp[v],edge(u,v).cost);

  这样子只是求出了删除所有叶子结点的总的最小花费。

  还有一个要求是最大值最小,最大值最小,一般都是用二分枚举最大值。

  那么状态转移方程就改为了  if(edge(u,v).cost > mid)  dp[u] += dp[v];  如果边的权值大于我们枚举的最大值,那么这条边就是不可取的

               else dp[u] +=min(dp[v],edge(u,v).cost);

 //切断某些边,使得所有的叶子结点与根结点不联通,这些权值之和要小于m,且是的所有切断的边中的权值的最大值最小

 /*
dp[i] += min(dp[son],edge(i->son)) 二分枚举边权值的最大值
存在性问题都是用二分枚举
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#include <iostream>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <string>
#include <math.h>
using namespace std;
typedef long long LL;
const int INF = ;//这个不能太大,因为有可能所有的边都不可取,然后dp[u] += dp[v] ,那么dp[u]就加了很多的INF
const int N = +;
struct node
{
int v,cost;
};
vector<node> g[N];
int dp[N];
int mid;
int ans; void dfs(int u, int fa)
{
dp[u] = ;
bool flag = false;
for(int i=; i<g[u].size(); ++i)
{
int v = g[u][i].v;
if(v==fa) continue;
flag = true;
dfs(v,u);
// 如果边的权值大于我们枚举的最大值,那么这条边就是不可取的
if(g[u][i].cost > mid) dp[u] += dp[v];
else
dp[u] += min(dp[v],g[u][i].cost); }
if(!flag)
dp[u] = INF;
}
int main()
{
int n,m,i,a,b,c;
node tmp;
while(scanf("%d%d",&n,&m),n)
{
for(i=; i<=n; ++i)
g[i].clear();
for(i=; i<n; ++i)
{
scanf("%d%d%d",&a,&b,&c);
tmp.cost = c;
tmp.v = a;
g[b].push_back(tmp);
tmp.v = b;
g[a].push_back(tmp);
}
int low = ,high = m;
ans = INF;
while(low<=high)
{
mid = (low + high) >> ;
dfs(,-);
if(dp[]<=m)//如果按照我们枚举的最大值仍然能使得总花费小于m,那么该最大值是可行的
{
ans = mid;
high = mid - ;
}
else
low = mid + ;
}
if(ans == INF)
puts("-1");
else
printf("%d\n",ans);
}
return ;
}

poj3107

给定n和n-1条边。

问删除哪个点,使得剩下的联通分支,结点个数最大的最小。

num[u] 表示子树u有多少个结点,dp[u] 表示删除这个结点后,剩下的最大的联通分支的结点个数

从结点u出发的所有分支获得个数最多的联通分支

(树的重心即删除重心后,会使得最大的联通分支的结点个数最小)

if(v==fa)

dp[u] = max(dp[u],n-num[u]);//

else

dp[u] = max(dp[u],num[v]);

注意:这里用STL会超时。

 #include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#include <iostream>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <string>
#include <math.h>
using namespace std;
typedef long long LL;
const int INF = <<;
const int N = + ;
int num[N];
int dp[N];
int n;
int head[N],e;
struct node
{
int v,next;
}g[N*];
void addEdge(int a, int b)
{
g[e].v = b;
g[e].next = head[a];
head[a] = e++;
}
void dfs1(int u, int fa)
{
num[u] = ;
for(int i=head[u]; i!=-; i=g[i].next)
{
int v = g[i].v;
if(v==fa) continue;
dfs1(v,u);
num[u] += num[v];
}
}
void dfs2(int u, int fa)
{
dp[u] = ;
for(int i=head[u]; i!=-; i=g[i].next)
{
int v = g[i].v;
if(v==fa)
{
dp[u] = max(dp[u],n-num[u]);
}
else
{
dfs2(v,u);
dp[u] = max(dp[u],num[v]);
}
}
}
int main()
{
int i,u,v;
while(scanf("%d",&n)!=EOF)
{
e = ;
for(i=; i<=n; ++i)
head[i] = -;
for(i=; i<n; ++i)
{
scanf("%d%d",&u,&v);
addEdge(u,v);
addEdge(v,u);
}
dfs1(,-);
dfs2(,-);
int ans = INF;
for(i=; i<=n; ++i)
ans = min(ans,dp[i]);
for(i=; i<=n; ++i)
if(ans==dp[i])
{
printf("%d",i);
break;
}
for(i+=;i<=n; ++i)
if(ans==dp[i])
printf(" %d",i);
puts(""); }
return ;
}

poj http://poj.org/problem?id=2378

给定n和n-1条边。

问删除哪些点使得最大的联通分支的结点个数小于总结点的一半。

和上面那题一样

 #include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#include <iostream>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <string>
#include <math.h>
using namespace std;
typedef long long LL;
const int INF = <<;
const int N = + ;
vector<int> g[N];
int num[N],dp[N];
int n;
void dfs1(int u, int fa)
{
num[u] = ;
for(int i=; i<g[u].size(); ++i)
{
int v = g[u][i];
if(v==fa) continue;
dfs1(v,u);
num[u] += num[v];
}
}
void dfs2(int u, int fa)
{
dp[u] = ;
for(int i=; i<g[u].size(); ++i)
{
int v = g[u][i];
if(v==fa)
{
dp[u] = max(dp[u],n-num[u]);
}
else
{
dp[u] = max(dp[u],num[v]);
dfs2(v,u);
}
}
}
int main()
{
int u,v,i;
while(scanf("%d",&n)!=EOF)
{
for(i=; i<=n; ++i)
g[i].clear();
for(i=; i<n; ++i)
{
scanf("%d%d",&u,&v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs1(,-);
dfs2(,-);
int half = n / ;
bool flag = false;
for(i=; i<=n; ++i)
if(dp[i] <= half)
{
flag = true;
printf("%d\n",i);
}
if(!flag)
puts("NONE");
}
return ;
}

poj3140 http://poj.org/problem?id=3140

给定n个结点,m条边。

给定n个结点的权值

给定m条边

要求我们删除一条边,树分为两个部分,要求两个部分的权值之差最小。

思路:两次dfs,第一次dfs求出以该结点为根结点的子树的权值

    第二次dfs枚举删除哪条边,然后记录树的两个部分的差值

    最后一次遍历,找出最小的差值即可

  

 #include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#include <iostream>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <string>
#include <math.h>
using namespace std;
typedef __int64 LL;
const int INF = <<;
const LL LLINF = ((LL))<<;
const int N = + ;
vector<int> g[N];
LL val[N];
LL dp[N];
LL sum;
//题意:分成两个部分,使得两个部分的权值之差尽可能小
void dfs1(int u, int fa)
{
for(int i=; i<g[u].size(); ++i)
{
int v = g[u][i];
if(v==fa) continue;
dfs1(v,u);
val[u] += val[v];//求出以u为根结点的子树的权值之和
}
}
LL myAbs(LL a)
{
if(a < )
return -a;
return a;
}
void dfs2(int u, int fa)
{
dp[u] = sum;
for(int i=; i<g[u].size(); ++i)
{
int v = g[u][i];
if(v==fa)
{
continue;
}
else//枚举删除边,然后用dp[u]记录最小值
{
dp[u] = min(dp[u],myAbs(sum-val[v]-val[v]));
dfs2(v,u);
}
}
}
int main()
{
int n,m;
int i,u,v;
int t = ;
while(scanf("%d%d",&n,&m),n)
{
sum = ;
for(i=; i<=n; ++i)
{
g[i].clear();
scanf("%I64d",&val[i]);
sum += val[i];
}
for(i=; i<m; ++i)
{
scanf("%d%d",&u,&v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs1(,-);
dfs2(,-);
LL ans = sum;
for(i=; i<=n; ++i)
ans = min(ans,dp[i]);
printf("Case %d: %I64d\n",t++,ans); }
return ;
}

树形背包类:

选或者不选的01背包类型,那么每个点枚举容量,同时为下一个点枚举容量即可

hdu1011 http://acm.hdu.edu.cn/showproblem.php?pid=1011

给定n个洞穴和m个士兵(每个士兵能消灭20个bugs)

然后给定每个洞穴的bugs数量(背包的费用)和brain的数量(背包的价值)

然后给定n-1条边,使得n个洞穴形成一课树

问能取得的brain数量的最大值。

思路:其实就是在树上面进行背包问题的求解,只不过是有依赖的背包(儿子要选,当且仅当父亲也被选)

题解  :http://www.cnblogs.com/justPassBy/p/4437436.html

hdu1561 http://acm.hdu.edu.cn/showproblem.php?pid=1561

思路:如果a=0,那么就建立一个根结点,让它指向a=0的结点。那么就可以变成一棵树了(同时m++)

变成树,每个结点选或者不选,就是01背包类型的树形dp,和上面那题一样

题解:http://www.cnblogs.com/justPassBy/p/4437849.html

hdu1520 http://acm.hdu.edu.cn/showproblem.php?pid=1520

题意是给定一棵树,每个结点有一个价值,要我们选择任意个结点使得总价值最大,规则是如果父亲结点被选了,那么儿子结点不可以被选,但是儿子的儿子可以被选

dp[u][1] 表示选择结点u可以获得的最大价值

dp[u][0]表示不选择结点u可以获得的最大价值

状态转移方程是 dp[u][1] = max( dp[u][1], dp[u][1]+dp[v][0] ); dp[u][0] = max( dp[u][0],max(dp[u][0]+dp[v][1], dp[u][0]+dp[v][0]) );

题解:http://www.cnblogs.com/justPassBy/p/4439068.html