树形DP总结(入门6题)

1、POJ-2342

在一个公司中,每个职员有一个快乐值ai,现在要开一个party,邀请了一个员工就不可能邀请其直属上司,同理邀请了一个人就不可以邀请其的直属员工,

问如何使得这个快乐值达到最大。

显然简单树形dp,对每个结点dp[i][0]表示不邀请这个员工,其子树达到的最大快乐值,dp[i][1]表示邀请i员工其子树达到的最大值。

dp[i][0]=(i的全部员工的max(dp[u][1],dp[u][0)相加,也就是其子员工来或不来的最大快乐值。

dp[i][1]=(i的全部员工的dp[u][0相加,也就是其子员工都不能不来的最大快乐值。

从大boss开始dp,最终结果就是max(dp[root][0],dp[root][1])

同样的题目在hdu也有:
HDU 1520   Anniversary party

题目是说有N个人参加party,每个人有一个rating值(可以理解为权值)和一个up(上司的编号),为了保证party的趣味性,每一个人不可以和他的直接上司都参加,问最后的rating和最大

这是一个典型的树形DP,DP[u][0]表示u不参加那他的这棵子树上的最大权值,DP[u][1]表示u参加时的这棵树上的最大权值,

每个U节点有两种选择  参加party时的该他的树的权值(dp[u][1]),不去参加party该树的权值(dp[u][0]);

当他(u)不去时,他本身的权值(dp[u][0])就等于  他所有后面节点(儿子节点,后继节点)最大的权值(他儿子节点有两种情况,去或不去,所以是求最大值之和)dp[u][0] += max(dp[v][1], dp[v][0]);

当(u)去时,它本身的权值dp[u][1])只能加上他所有后面节点(儿子节点,后继节点)不去时的权值。dp[u][1] += dp[v][0];

v是从u读出来的 后面节点(儿子节点,后继节点)。

代码链接 https://blog.csdn.net/readlnh/article/details/52226453

Sample Input


 
7

1

1

1

1

1

1

1

1 3

2 3

6 4

7 4

4 5

3 5

0 0

Sample Output

5

#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=6000+10;
vector<int>G[maxn];
int dp[maxn][2],n,u,v,f[maxn];
void dfs(int root)
{
	for(int i=0;i<G[root].size();i++)
	{
		int v=G[root][i];
		dfs(v);
		dp[root][0]+=max(dp[v][0],dp[v][1]);
		dp[root][1]+=dp[v][0];
	}
}
int main()
{
	scanf("%d",&n);
	memset(dp,0,sizeof(dp)); 
	memset(f,-1,sizeof(f));
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&dp[i][1]);
		G[i].clear();
	}
	while(scanf("%d%d",&u,&v)&&u&&v)
	{
		G[v].push_back(u);
		f[u]=v;
	}
	int root=1;
	while(f[root]!=-1) root=f[root];
	dfs(root);
	printf("%d",max(dp[root][1],dp[root][0]));
	return 0;
}

2、POJ-1463

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

还是简单树形dp,状态转移方程好写。

dp[i][0]表示这个点不选(说明子结点全部选),因为只有子结点全部选,才能确保这个点到子结点的全部边都有士兵守卫。

dp[i][1]表示这个点选,那么子结点就是+min(选/不选),然后对根结点一样是判断0与1的大小即可

#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=1500+10;
int n,f[maxn],v,num,u,dp[maxn][2];
vector<int>G[maxn];
void dfs(int root)
{
	for(int i=0;i<G[root].size();i++)
	{
		int v=G[root][i];
		dfs(v);
		dp[root][1]+=min(dp[v][0],dp[v][1]);
		dp[root][0]+=dp[v][1];
	}
}
int main()
{
	while(~scanf("%d",&n)){
		memset(dp,0,sizeof(dp));
		for(int i=0;i<=n;i++) dp[i][1]=1,f[i]=-1,G[i].clear();
		for(int i=1;i<=n;i++)
		{
			scanf("%d:(%d)",&u,&num);
			for(int j=1;j<=num;j++)
			{
				scanf("%d",&v);
				G[u].push_back(v);
				f[v]=u;
			}
		}
		int root=0;
		while(f[root]!=-1) root=f[root];
		dfs(root);
		printf("%d\n",min(dp[root][0],dp[root][1]));
	}
}

3、POj-2378

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

那么dp[i]表示这棵子树的下面的(包括其)的总结点数

如果一棵树其全部子树<=总/2,且总-其全部子树结点和-1<=总/2,那么这个结点就合适,用个数组来储存,最后排序即可

 这题需要注意的是,上面两个题是有向图的保存方式,这题属于无向图的保存方式,可以从任意一点出发,无需找到根节点

具体看代码:

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn=1e4+10;
vector<int >G[maxn],que;
int n,v,u;
int dfs(int root,int fa)
{
//	if(G[root].size()==1&&G[root][0]==fa) return 1;//可有可无,判断叶子节点 
	int mx=0,sum=1,u;
	for(int i=0;i<G[root].size();i++)
	{
		int v=G[root][i];
		if(v==fa) continue;
		u=dfs(v,root);
		sum+=u;
		mx=max(mx,u);
	}
	mx=max(mx,n-sum);
	if(mx<=n/2)
	{
		que.push_back(root);
	}
	return sum;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		scanf("%d%d",&u,&v);
		G[u].push_back(v);
		G[v].push_back(u);
	}
	int ans=dfs(1,-1);
	if(que.size()==0)printf("NONE");
	else
	{
		sort(que.begin(),que.end());
		for(int i=0;i<que.size();i++)
		printf("%d\n",que[i]);
	}
}

4、ZOJ-3201***(agin)

给一棵树,问这棵树大小为k的子树最大的权值和是多少。

这题稍微有点难度,dp[i][j]表示i结点长度为j(包括其)的最大权值是多少。

然后对于每个其子结点就进行背包dp,判断这个结点对于长度为l的权值是否应该要(这里需要注意的就是一个结点不能要多次,所以应该从大到小dp(类似背包),而且要一个数组储存最初状态,比如这个根结点只有长为1的链,但是这个子结点长为3,计算到2的时候这个根结点已经有长度为3的结点了,再计算子结点为3的时候,发现根结点有3长度的结点,就会出现这个根结点存在长度为6的链,显然这是错误的)。

这个题属于在树上的dp,我也是理解很久才懂,有点难度,跟01背包相似又有区别

两个代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
#define mst(s, t) memset(s, t, sizeof(s))
const int INF = 0x3f3f3f3f;
const int maxn = 110;
vector<int> G[maxn];
int dp[maxn][maxn];  //dp[i][j]:node[i]结点数为j的子树的最大权值
int k, ans, cnt[maxn], weight[maxn];
int dfs(int node, int father)
{
    cnt[node] = 1;
    for(int i=0; i<G[node].size(); i++)
	{
        if(G[node][i] == father) continue;
        cnt[node] += dfs(G[node][i], node);
    }
    dp[node][1] = weight[node];
    //这里初始化不能在main()内  ?? 
/*
 *  dp[node][j-t]是之前的子节点为根更新的子树产生的
 *  dp[v][t]是以当前子节点为根的子树产生的
 *  j如果顺序遍历,前面dp[node][j]的更新会影响后面的dp[node][j-t],导致后面
 * 更新dp[node][j]时是一当前子节点为根的子树产生的
*/
    for(int i = 0; i < G[node].size(); i++)//node周围的
	{
        int v = G[node][i];//node的儿子 
        if(v==father) continue; 
        for(int j = cnt[node]; j >= 1; j--)
		{
            for(int t = 0; t<j && t<=cnt[v]; t++)
			{
                dp[node][j] = max(dp[node][j], dp[node][j-t]+dp[v][t]);
            }
        }
    }
    ans = max(ans, dp[node][k]);
    return cnt[node];
}

int main() 
{
    int n;
    while(scanf("%d%d",&n, &k) != EOF)
	{
        mst(dp, 0);  ans = 0;
        for(int i=0; i<maxn; i++)
		{
            G[i].clear();
        }
        for(int i=0; i<n; i++)
		{
            scanf("%d", &weight[i]);
        }
        int a, b;//无向图的保存方式 
        for(int i = 1; i < n; i++)
		{
            scanf("%d%d", &a, &b);
            G[a].push_back(b);
            G[b].push_back(a);
        }
        dfs(0, -1);
        printf("%d\n", ans);
    }
    return 0;
}
#include<bits/stdc++.h>
using namespace std;
int d[105][105],a[105],n,k,ans;
vector<int>G[105];
void dfs(int u,int fa)
{
	d[u][1]=a[u];
	for(int i=0;i<G[u].size();i++)
	{
		int v=G[u][i];
		if(v==fa)continue;
		dfs(v,u);
		for(int j=k-1;j>0;j--)
		for(int p=1;p+j<=k;p++)
		d[u][j+p]=max(d[u][j+p],d[u][j]+d[v][p]);
	}
	ans=max(ans,d[u][k]);
}
int main()
{
	int u,v;
	while(~scanf("%d%d",&n,&k))
	{
		for(int i=0;i<n;i++)
		{
			G[i].clear();
			for(int j=1;j<=n;j++)d[i][j]=0;
			scanf("%d",&a[i]);
		}
		for(int i=1;i<n;i++)
		{
			scanf("%d%d",&u,&v);
			G[u].push_back(v);
			G[v].push_back(u);
		}
		ans=0;
		dfs(0,-1);
		printf("%d\n",ans);
	}
}

5 - Cell Phone Network

题意:给n[1,10000]个点,n-1条边,树形结构,从n个点中取尽量少的点构成一个集合,使剩下所有点都能与这个集合中的部分点相连。

(这个概念叫最小支配集),第二题应该就是最小点覆盖了

这个题我不太会,待补(有点难)

F - Computer(待补,有难度)

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

猜你喜欢

转载自blog.csdn.net/qq_41286356/article/details/86673406