Codeforces Global Round 16 E-Buds Re-hanging 树上搜索/树上dp

题目链接

题目大意

给你一棵树 根节点为1

规定一种节点为树芽:

1树芽不能是根节点

2树芽不能是叶子节点

3树芽的所有子节点都是叶子节点

树芽可以任意移动 即:树芽可以切断自己与父节点的联系 然后带着自己的子孙们 链接任意节点

可以移动无数次

问你如何移动会让最后树的所有叶子节点最少

题目思路一

我们要推导出一个结论

一个芽节点的移动会导致叶子节点减少 当且仅当 该芽的父亲节点有其他的孩子

如下图中节点2即为可减节点

Codeforces Global Round 16 E-Buds Re-hanging 树上搜索/树上dp

如下图中4为不可减节点 但是当该节点消去之后 2节点就变为了可减节点

Codeforces Global Round 16 E-Buds Re-hanging 树上搜索/树上dp

于是我们进行树上搜索

ans=所有的叶子节点

对于每一个节点 分为三类

1第一类 是叶子节点

2第二类 是芽节点

3第三类 啥也不是

那么对于每个节点 搜索当前节点的子节点的各种节点数量

int tot=0;//总量
    int yz=0;//叶子
    int kq=0;//芽节点(可切)
    int qt=0;//其他

如果没有任何子节点则 为叶子节点 返回 1

若 所有节点都是叶子节点 则该节点为芽返回 2

都不是返回3

该节点的子节点存在可切子节点(即子节点为芽节点)

则需要将该节点进行处理

若所有子节点都可切 那么ans-=kq-1

为什么要减1 因为之前推出的结论 一个芽节点的移动会导致叶子节点减少 当且仅当 该芽的父亲节点有其他的孩子

减到最后父节点没有孩子节点了 当然无法减1了

若只有部分节点可切 那么ans-=kq 为什么不减1 因为无论如何减该芽的父节点都有孩子节点

剪完之后 在判断目前的点是什么点

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
int head[maxn],cnt,T,n;
int x,y,ans=0;
struct node{
	int to,next;
	int dis;
}e[maxn*2];
void add(int x,int y,int z)
{
	e[++cnt].dis=z;
	e[cnt].to=y;
	e[cnt].next=head[x];
	head[x]=cnt;
}
int dfs(int now,int fa)
{
	int u=now;
	int tot=0;//总量 
	int yz=0;//叶子 
	int kq=0;//芽节点(可切) 
	int qt=0;//其他 
	for(int i=head[u];i;i=e[i].next)
	{
		int v=e[i].to;
		if(v==fa)continue;
		tot++;
		int val=dfs(v,u);
		if(val==1)yz++;
		else if(val==2)kq++;
		else qt++;
	}
	//if(now==1)return 1;
	if(tot==0)return 1;//如果没有任何子节点则 为叶子节点 返回 1
	if(tot==yz)return 2;//若 所有节点都是叶子节点 则该节点为芽返回 2
	if(kq!=0)
	{
		if(kq==tot)
		{
			//若所有子节点都可切 那么ans-=kq-1
			ans-=tot-1;
			return 1;
		}
		//若只有部分节点可切 那么ans-=kq 为什么不减1 因为无论如何减该芽的父节点都有孩子节点
		ans-=kq;
		if(qt==0)
		{
			return 2;
		}
		else 
		{
			return 3;
		}
	}
	return 3;
}
void init()
{
	cnt=0;
	ans=0;
	for(int i=0;i<=n;i++)
	{
		head[i]=0;
	}
}
int main()
{
	cin>>T;
	while(T--)
	{
		cin>>n;
		init();
		for(int i=1;i<n;i++)
		{
			cin>>x>>y;
			add(x,y,1);
			add(y,x,1);
		}
		for(int i=1;i<=n;i++)//计算ans 
		{
			int num=0;
			for(int j=head[i];j;j=e[j].next)
			{
				int v=e[j].to;
				if(v==i)continue;
				num++;
			}
			if(num==1&&i!=1)ans++;
		}
		//cout<<ans<<endl;
		dfs(1,0);
		cout<<ans<<endl;
	}
	return 0;
}
/*
1
6
1 2
1 3
2 4
2 5
3 6
*/

题目思路二

队友写的树上dp

代码很详细

代码

#include<stdio.h>
#include<iostream>
#include<sstream>
#include<string>
#include<string.h>
#include<stdlib.h>
#include<queue>
#include<vector>
#include<bitset>
#include<algorithm>
#include<cmath>
#include<ctime>
#include<cctype>
#include<stack>
#include<set>
#include<map>
#include<unordered_map>
using namespace std;
typedef long long int ll;
const double pi=acos(-1.0);
const int INF=0x3f3f3f3f;
const double eps=1e-8;

const int N=2e5;
struct Edge{
	int from,to,np;
};
int dp[N+3];
Edge edge[2*N+3];
int tot,n;
int head[N+3];
void init();
void add(int x,int y);
int get_dp(int x,int father);
int main(){
	//ios::sync_with_stdio(false);
	int T;
	cin>>T;
	while(T--){
		init();
		scanf("%d",&n);
		for(int i=1;i<=n-1;i++){
			int x,y;
			scanf("%d%d",&x,&y);
			add(x,y);
			add(y,x);
		}
		get_dp(1,-1);
		printf("%d\n",dp[1]);
		
	}



	return 0;
}
void init(){
	tot=0;
	memset(dp,0,sizeof dp);
	memset(edge,0,sizeof dp);
	memset(head,-1,sizeof head);
}
void add(int x,int y){
	tot++;
	edge[tot].from=x;
	edge[tot].to=y;
	edge[tot].np=head[x];
	head[x]=tot;
}
int get_dp(int x,int father){//返回这个x节点是否可以切割,可以就返回1,否则为0 
	//printf("x=%d\n",x)
	int cnt=0;//记录不可切割的子节点的数量 
	for(int i=head[x];i!=-1;i=edge[i].np){
		int y=edge[i].to;
		if(y==father) continue;
		if(!get_dp(y,x)){
			dp[x]+=dp[y];//dp[x]表示以x为根节点的最少分割的叶子节点数量 
			cnt++;
		}else{
			dp[x]+=dp[y]-1;
		}

	}
	if(cnt==0) dp[x]++;
	//printf("dp[%d]=%d\n",x,dp[x]);
	if(cnt==0) return 0;
	else return 1;
}

上一篇:8.7 浅析图论最短路算法


下一篇:[scrapy] scrapy 使用goose作为正文提取