树形DP入门——我也不想学但是队友都不想学只能我上啊

今天晚上我做了几题树形dp的题 给大家露一手

POJ——Tree Cutting

题意:

一棵无向树,结点为n(<=10,000),删除哪些结点可以使得新图中每一棵树结点小于n/2。

这不是一道搜索题!这不是一道搜索题!这不是一道搜索题!

就是枚举每个点删去后的最大值。

判断最大值的要求  1.childmax<n/2 最大的子树满足条件  2.n-sum <= n/2 这个节点之前的树也满足条件

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn = 10010;
vector<int>g[maxn];
int fa[maxn],ans[maxn];
int n,num=0;
int dfs (int root ,int father)//dfs 找到叶节点向上回溯 
{
	int sum=1, childmax=0;//sum记录这颗子树的节点数 
	for(int i=0;i<g[root].size();i++)
	{
		if(g[root][i]!=father)//父节点就跳过,不在找一遍 
		{
			int son_sum= dfs(g[root][i],root);//其root的子树的总值 
			childmax= max(childmax,son_sum);// 找最大的 子数的节点数 
			sum += son_sum; //总子节点+本身 
		}
	}
	childmax = max(childmax,n-sum);//最大子树和这个点之前的树 
	if(childmax<=n/2)
	{
		ans[num++]=root;
	}
	return sum;//返回这个子树的总值 
} 
int main(){
	int x,y;
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		g[x].push_back(y);
		g[y].push_back(x);
	}
	dfs(1,0);//从1 开始dp 
	if(num){
		//按顺序输出 
		sort(ans,ans+num);
		for(int i= 0;i<num;i++)
			printf("%d\n",ans[i]);
	}
	else {
		printf("NONE\n");
	}
}

洛谷-P2016

现在要在一棵树上布置士兵,每个士兵在结点上,每个士兵可以守护其结点直接相连的全部边,问最少需要布置多少个士兵。

此题是一道经典的树形dp,每个点覆盖的是相邻的边,如果你会匈牙利算法也可以写(反正我不会)

定义状态dp[u][0/1]表示u这个节点不放/放士兵

对于节点u,如果这个点放士兵,则其子节点可以放也可以不放  dp[u][1]+= min(dp[v][1],dp[v][0])  v为u的子节点

如果u不放士兵其子节点就一定要放士兵 dp[u][0] += dp[v][1] 

因为树形dp自下而上的查找,只要考虑其子节点对其的影响即可。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1550;
vector<int>g[maxn];
int dp[maxn][2];
int fa[maxn];
void dfs (int root){
	for(int i=0;i<g[root].size();i++){
		dfs(g[root][i]);//dfs 到叶节点
	}
	for(int i=0 ;i<g[root].size();i++){
        //这棵树的最小节点
		dp[root][0] += dp[g[root][i]][1];
		dp[root][1] += min(dp[g[root][i]][0],dp[g[root][i]][1]);
	}
}
int main()
{
	int n;
	int x,k,ss;
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		g[i].clear();
		dp[i][1]=1;dp[i][0]= 0;//dp[i][1]取这个点
		fa[i]=-1;
	}
	for(int i=0;i<n;i++)
	{
		scanf("%d%d",&x,&k);
		for(int j=0;j<k;j++)
		{
			scanf("%d",&ss);
			g[x].push_back(ss);//放入其子树
			fa[ss]=x;
		}
	}
	int root = 1;
	while(fa[root]!=-1)root = fa[root]; //找一个根节点
	dfs(root);
	printf("%d\n",min(dp[root][0],dp[root][1]));
	return 0;
}

POJ-2378

题意

给一一颗树,问删除哪些结点可以使得剩下的子图的结点数<=总/2。

这个和上一个有点像,但是是覆盖点,不是覆盖边,

定义dp[u][0/1/2]

dp[u][0]: u不放士兵,父节点覆盖

dp[u][1]: u不放士兵,子节点覆盖                 

dp[u][2]: u放士兵 

dp[u][0] += min (dp[v][1],dp[v][2]);

dp[u][2] +=min(dp[v][1],dp[v][2],dp[v][0])+1;

dp[u][1]  =min(dp[v][1],dp[v][2])

子节点v不存在被染色的父亲;若所有v均不染色,此时u未被覆盖,故需要有一个v来染色,选择min(dp[v][2]-dp[v][1])即可。

     

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int Inf = 0x3f3f3f3f;
const int maxn = 10010;
vector<int>g[maxn];
int dp[maxn][3]={0};
int fa[maxn];
void dfs (int root,int father){
	if(g[root].size() == 1&& g[root][0]==father){
		dp[root][0]=0;
		dp[root][1]=Inf;
		dp[root][2]=1;
		return ;
	}
	
	int mini= Inf,flag=1;
	
	for(int i=0;i<g[root].size();i++)
	{
		int v =g[root][i];
		if(v==father)continue;
		dfs(v,root);
		
		dp[root][0] += min(dp[v][1],dp[v][2]);
		dp[root][2] += min(min(dp[v][1],dp[v][2]),dp[v][0]);
			
    	if(dp[v][1] < dp[v][2])
		{
            //
            dp[root][1] += dp[v][1];
            mini = min(mini, (dp[v][2] - dp[v][1]));
        }
	    else{
              	flag = 0;
              	dp[root][1] += dp[v][2];
        }
	}
		 	
	dp[root][2]++; //这个点要加1; 
    //如果所有子节点都没有覆盖root就放最小的点
    if(flag)   dp[root][1] += mini;
}
int main()
{
	int n;
	int x,y;
	scanf("%d",&n);
	for(int i=0;i<=n;i++)g[i].clear();
	for(int i=1;i<n;i++)
	{
		scanf("%d%d",&x,&y);
			g[x].push_back(y);
			g[y].push_back(x);
	}
	dfs(1,0);
	
	printf("%d\n",min(dp[1][1],dp[1][2]));
	return 0;
}

HUD-2196

题意:一棵边带权值的树,求每个点在树上的最远距离。

有点像上一题,这个是带全值的树

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn = 10010;
struct edge{
    int next,w;
};
vector<edge>g[maxn];
int id[maxn];
int dp[maxn][3];
void dfs (int root ,int father)
{
    for(int i=0;i<g[root].size();i++)
    {
        int next = g[root][i].next,w=g[root][i].w;
        
        if(next == father)continue;
        dfs(next,root);
        //子树的max 
        if(dp[root][0]<dp[next][0]+w)
        {
            dp[root][0]= dp[next][0]+w;
            id[root] = next;
        }
    }
    
    for(int i=0;i<g[root].size();i++)
    {
        int next = g[root][i].next,w=g[root][i].w;
        //子树的次max 
        if(next == father||next==id[root])continue;
        dfs(next,root);
        dp[root][1]=max(dp[root][1],w+dp[next][0]);
    }
} 
void dfs2(int root,int father)
{
    for(int i=0;i<g[root].size();i++)
    {
        int next = g[root][i].next,w=g[root][i].w;
        if(next == father)continue;
        if(next==id[root])//在最长就取次长 
        {
            dp[next][2] = max(dp[root][2],dp[root][1]) + w ;
        } 
        else {//取最长 
            dp[next][2]= max(dp[root][2],dp[root][0]) + w; 
        }
        dfs2(next,root);
    }
}
int main(){
    int n;
    int father,w;
    while(~scanf("%d",&n))
    {
        for(int i =1;i<=n;i++)
        {
            g[i].clear();
        } 
        memset(id,0,sizeof id);
        memset(dp,0,sizeof dp);
        for(int i=2;i<=n;i++)
        {
            scanf("%d%d",&father,&w);
            edge ss,tt;
            ss.w=w;tt.w=w;
            ss.next= father,tt.next=i;
            g[father].push_back(tt);
            g[i].push_back(ss);
        }
        dfs(1,-1);
        dfs2(1,-1);
        for(int i=1;i<=n;i++)
        {
            printf("%d\n",max(dp[i][2],dp[i][0]));
        }
    }

}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值