树形dp

目录

一:黑白树(牛客网)

二:求最小点覆盖----树形dp

三:求最大独立集----树形dp
四:I - Tree Cutting (Easy Version)


一:黑白树(牛客网)

题目链接

时间限制:C/C++ 1秒,其他语言2秒

空间限制:C/C++ 32768K,其他语言65536K

64bit IO Format: %lld

题目描述 

一棵n个点的有根树,1号点为根,相邻的两个节点之间的距离为1。树上每个节点i对应一个值k[i]。每个点都有一个颜色,初始的时候所有点都是白色的。
你需要通过一系列操作使得最终每个点变成黑色。每次操作需要选择一个节点i,i必须是白色的,然后i到根的链上(包括节点i与根)所有与节点i距离小于k[i]的点都会变黑,已经是黑的点保持为黑。问最少使用几次操作能把整棵树变黑。

输入描述:

第一行一个整数n (1 ≤ n ≤ 10^5)
接下来n-1行,每行一个整数,依次为2号点到n号点父亲的编号。
最后一行n个整数为k[i] (1 ≤ k[i] ≤ 10^5)

样例解释:
对节点3操作,导致节点2与节点3变黑
对节点4操作,导致节点4变黑
对节点1操作,导致节点1变黑

输出描述:

一个数表示最少操作次数

示例1

输入

复制

4
1
2
1
1 2 2 1

输出

复制

3
#include<bits/stdc++.h>
using namespace std;
const int N  = 1e5+100;
int n;
vector<int> v[N];
int k[N],fa[N];
int ans=0;
int dfs(int x){
	//now表示剩余的染色距离 
    int now = 0, siz = v[x].size();
    for(int i=0;i<siz;i++)
        now=max(now,dfs(v[x][i]));
    //该点染不到,就计数,返回该点所能到达的最大染色距离 
    if(now<=0){
        ans++;
        return k[x]-1;
    }
    //回溯过程更新父节点的染色距离已达到最大染色距离 
    k[fa[x]]=max(k[fa[x]],k[x]-1);
    return now-1;
}

int main(){
    int n;
    scanf("%d", &n);
    for(int i=2;i<=n;i++){
        int d;
        scanf("%d", &d);
        v[d].push_back(i);
        fa[i]=d;
    }
    for(int i=1;i<=n;i++)
        scanf("%d", &k[i]);
    dfs(1);
    printf("%d\n",ans);
}

二:求最小点覆盖----树形dp

 

鲍勃喜欢玩电脑游戏,特别是战略游戏,但有时他找不到足够快的解决办法,然后他很难过。现在他有了以下问题。他必须保卫一座中世纪的城市,这座城市的道路形成了一棵树。他必须把最小数量的士兵放在节点上,这样他们才能观察到所有的边缘。你能帮他吗?

您的程序应该找到Bob必须为给定的树设置的最小士兵数。

例如,对于树:


解决方案是一名士兵(在节点1处)。

输入

输入包含几个文本格式的数据集。每个数据集表示一棵树,描述如下:

  •  
  • 节点数
  • 以下列格式描述每个节点
    Node_标识符:(Number_Of_Road)节点_标识符1节点标识符2..节点标识符道路数目 

    节点_标识符:(0)


节点标识符为0到n-1之间的整数,对于n个节点(0<n<=1500);每一行输入中的道路数不超过10。输入数据中每条边只出现一次。

输出量

输出应打印在标准输出上。对于每个给定的输入数据集,在给出结果的一行中打印一个整数(士兵的最小数量)。举例如下:

样本输入

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<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<vector>
#include<cmath>
#include<string>
#include<map>
#include<queue>
using namespace std;
typedef long long ll;
//求一棵树的最小点覆盖
//dp[u][0]表示节点u属于点覆盖集合
//dp[u][1]表示节点u不属于点覆盖集合 
//连个重要的状态转移方程
//dp[u][0]+=min(dp[v][0],dp[v][1]);
//dp[u][1]+=dp[v][0];

ll dp[1500][2]; 

vector<ll>p[1500];

void dfs(ll u,ll pa){
	//初始化 
	dp[u][0]=1;
	dp[u][1]=0;
	ll len=p[u].size();
	for(ll i=0;i<len;i++){
		ll v=p[u][i];
		//如果找到自己的父节点,不做处理 
		if(v==pa)continue; 
		dfs(v,u);//算出以v为根节点的情况来更新父节点u 
		dp[u][0]+=min(dp[v][0],dp[v][1]);
		dp[u][1]+=dp[v][0];
	}
}


int main(){
	ll n;
	while(scanf("%lld",&n)!=EOF){
		for(ll i=0;i<n;i++){
			p[i].clear();
		}
		ll x,t;
		for(ll i=0;i<n;i++){
		scanf("%lld:(%lld)",&x,&t);
		for(ll j=0;j<t;j++){
			ll y;
			scanf("%lld",&y);
			p[x].push_back(y);
			p[y].push_back(x);
		}
		}
		memset(dp,0,sizeof(dp));
		
	dfs(0,0);
	cout<<min(dp[0][0],dp[0][1])<<endl;
	}
	return 0;
}

三:求最大独立集----树形dp

 

亲爱的选手,

我将在我的别墅在Hali-Bula举行一个聚会,以庆祝我从业连管退休。我希望我能邀请我所有的同事,但是想象一下,当一个员工在客人中找到他的老板时,他会怎样享受一个聚会!所以,我决定不邀请员工和他/她的老板。在业连管的组织层次是这样的,没有人有超过一个老板,而且只有一个员工没有老板(大老板)!我可以请你写一个程序来确定客人的最大数量,这样当他/她的老板也被邀请的时候,就不会有员工被邀请了吗?我已经附上了员工名单和业连管的组织等级。

最好,
-布莱恩·班尼特

如果你的节目能说明如果我选择邀请有这种条件的客人的最大数量,那么这个名单是否是唯一确定的,我将不胜感激。

输入

输入由多个测试用例组成。每个测试用例都由一个包含整数的行开始。n(1≤)n(≤200),业连管的雇员人数。下一行只包含“大老板”的名称。以下每一项n-一行包括雇员的姓名和老板的姓名。所有的名字都是至少一个字串,最多100个字母,用空格隔开。每个测试用例的最后一行包含一个0。

输出量

对于每个测试用例,编写一行,其中包含一个数字,指示根据所需条件可以邀请的最大来宾数,以及一个单词“是”或“否”,这取决于在这种情况下来宾列表是否是唯一的。

样本输入

6
Jason
Jack Jason
Joe Jack
Jill Jason
John Jack
Jim Jill
2
Ming
Cho Ming
0

样本输出

4 Yes
1 No
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<vector>
#include<cmath>
#include<string>
#include<map>
#include<queue>
using namespace std;
typedef long long ll;
vector<ll> p[201];
//dp[i][0]表示不取i节点
//dp[i][1]表示取i节点
//dup[i][0]表示不取i节点是否唯一,等于0表示不唯一,等于1表示唯一
//dup[i][1]表示取i节点是否唯一,,,,,, 
ll dp[201][2],dup[201][2]; 
map<string,ll> mp;
//在一棵树中找不相邻的节点,求最大的数量,并判断方案是否唯一
//找最大独立集问题 

void dfs(ll root){
	dp[root][1]=1;
	dp[root][0]=0;
	dup[root][0]=1;
	dup[root][1]=1;
	ll len=p[root].size();
	for(ll j=0;j<len;j++){
		ll u=p[root][j];
		dfs(u);
		dp[root][0]+=max(dp[u][0],dp[u][1]);
		dp[root][1]+=dp[u][0];
		//判唯一性:根据子节点的情况决定 
		//不选root节点的唯一性判断 
		if(dp[u][0]>dp[u][1]&&dup[u][0]==0){
			dup[root][0]=0;
		}
		else if(dp[u][1]>dp[u][0]&&dup[u][1]==0){
			dup[root][0]=0;
		}
		else if(dp[u][0]==dp[u][1]){
			dup[root][0]=0;
		}
		//选root节点的唯一性判断 
		if(dup[u][0]==0){
			dup[root][1]=0;
		}
	}
}


int main(){
	ll n;
	while(cin>>n&&n){
		for(ll i=1;i<=200;i++){
			p[i].clear();
		}
		mp.clear();
		string a,b;
		cin>>a;
		ll cnt=1;
		if(!mp[a]){
			mp[a]=cnt++;
		}
		for(ll i=1;i<n;i++){
			cin>>a>>b;
			if(!mp[a]){
				mp[a]=cnt++;
			}
			if(!mp[b]){
				mp[b]=cnt++;
			}
			p[mp[b]].push_back(mp[a]);
		}
		memset(dp,0,sizeof(dp));
		memset(dup,0,sizeof(dup));
		dfs(1);
		//求最优方案:max(dp[1][0],dp[1][1]); 
		if(dp[1][0]>dp[1][1]&&dup[1][0]==1){
			cout<<dp[1][0]<<" Yes"<<endl;
		}
		else if(dp[1][1]>dp[1][0]&&dup[1][1]==1){
			cout<<dp[1][1]<<" Yes"<<endl;
		}
		else cout<<max(dp[1][1],dp[1][0])<<" No"<<endl;
	}
	return 0;
}

三: I - Tree Cutting (Easy Version)----链接

输出量

标准输出

给你一棵没有方向的树nn顶点。

有些顶点是蓝色的,有些是红色的,有些是未着色的。保证树包含至少一个红色顶点和至少一个蓝色顶点。

你选择一个边缘并将它从树中移除。树被分成两个相连的组件。让我们说一句“边缘”好的,漂亮的如果两个结果组件都不包含红色和蓝色的顶点。

多少好的,漂亮的在给定的树中有边缘吗?

输入

第一行包含一个整数。nn (2≤n≤3⋅1052≤n≤3⋅105)-树中的顶点数。

第二行包含nn整数a1,a2,…,ana1,a2,…,an (0≤ai≤20≤ai≤2)-顶点的颜色。ai=1ai=1意味着那个顶点ii是红色的,ai=2ai=2意味着那个顶点ii是蓝色的ai=0ai=0意味着那个顶点ii是没有颜色的。

这个ii-下一个n−1n−1行包含两个整数vivi和uiui (1≤vi,ui≤n1≤vi,ui≤n, vi≠uivi≠ui)-树的边缘。保证给定的边形成一棵树。保证树包含至少一个红色顶点和至少一个蓝色顶点。

输出量

打印一个整数-好的,漂亮的给定树的边缘。

实例

输入

复制

5
2 0 0 1 2
1 2
2 3
2 4
2 5

输出量

复制

1

输入

复制

5
1 0 0 0 2
1 2
2 3
3 4
4 5

输出量

复制

4

输入

复制

3
1 1 2
2 3
1 3

输出量

复制

0

下面是第一个示例中的树:

唯一好的,漂亮的边是边(2,4)(2,4)..移除它会使树分崩离析。{4}{4}和{1,2,3,5}{1,2,3,5}..第一分量只包括红色顶点,第二分量包括蓝色顶点和未着色顶点。

下面是第二个示例中的树:

每一条边都是好的,漂亮的在里面。

下面是第三个例子中的树:

边缘(1,3)(1,3)将其分解为组件{1}{1}和{3,2}{3,2},后者既包括红色顶点,也包括蓝色顶点,因此边不是好的,漂亮的..边缘(2,3)(2,3)将其分解为组件{1,3}{1,3}和{2}{2},前者包括红色和蓝色顶点,因此边也不是好的,漂亮的..答案是0。

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<vector>
#include<cmath>
#include<string>
#include<map>
#include<queue>
using namespace std;
typedef long long ll;
ll r,b,n;
struct node{
	ll r,b;//红色,蓝色 
}num[300001];
vector<ll> p[300001];

void dfs(ll u,ll fa){
	ll len=p[u].size();
	for(ll i=0;i<len;i++){
		ll v=p[u][i];
		if(v==fa)continue;
		dfs(v,u);
		//合并颜色 
		num[u].r+=num[v].r;
		num[u].b+=num[v].b;
	}
}
ll ans;
void dfs_plus(ll u,ll fa){
	ll len=p[u].size();
	for(ll i=0;i<len;i++){
		ll v=p[u][i];
		if(v==fa){
			continue;
		}
		if(num[v].b==b&&num[v].r==0||num[v].r==r&&num[v].b==0){
			ans++;
		}
		dfs_plus(v,u);
	}
}

int main(){
	ll pic;
	cin>>n;
	for(ll i=1;i<=n;i++){
		cin>>pic;
		if(pic==1){
			r++;
			num[i].r++;
		}
		else if(pic==2){
			b++;
			num[i].b++;
		}
	}
	ll x,y;
	for(ll i=1;i<n;i++){
		cin>>x>>y;
		p[x].push_back(y);
		p[y].push_back(x);
	}
	ans=0;
	dfs(1,0);
	dfs_plus(1,0);
	cout<<ans<<endl;
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值