2019 CCPC Final K - Russian Dolls on the Christmas Tree

~模板题qwq
题意:在一棵树中,假如某些节点的序号值是相连的,则可以把他们连接在一起,然后问你每一棵子树中的所有节点(包括当前节点)经过连接之后最后能剩余几段。

思路:很明显是一个树上启发式合并的模板题。我们只需要用一个vis数组记录,然后没走到一个节点u的时候,去看vis[u+1]和vis[u-1]这两个节点是否存在,对应就会出现三种情况:

1.对应的前驱和后继节点都没有出现过,可知此时在当前子树中,这个节点是孤立的,所以就目前来看他最后会形成一个单独的段,答案+ +

2.对应的前驱和后继节点都出现过了,可知这时它会和那两个节点形成一个连续的段,并且那两个点已经会把他们当做孤立的段增加答案了,所以这时需要对答案- -

3.对应的前驱和后继节点其中有一个出现过另一个没有出现过,这时直接可以把当前节点和它出现过得那个点连接起来相当于答案没有影响,不用进行什么操作。

这样每次走到一个节点时统计一下答案即可,即稍微修改一下统计函数即可。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int MAXN = 2e5+7;
int head[MAXN],tot,siz[MAXN],son[MAXN],vis[MAXN],flag;
int sum,ans[MAXN];
//对面当前节点u如果他的 前驱节点 和 后驱节点都没有出现的话 代表这个节点 当前来看只能是一个孤立的点
//如果他的前驱和后继同时都存在 那么证明 之前某一个孤立的节点 可以被连接起来
//如果只是前驱存在或者后继存在的话 那么就证明这个节点 可以被附在 某个节点的后面 答案不需要进行改动

struct node{
	int to,next;
}edge[MAXN<<1];

void addedge(int u,int v){
	edge[++tot].to = v;
	edge[tot].next = head[u];
	head[u] = tot;
}

void pre_dfs(int u,int fa){//求数树的重儿子
	siz[u] = 1;
	for(int i = head[u];i;i = edge[i].next){
		int v = edge[i].to;
		if(v == fa) continue;
		pre_dfs(v,u);
		siz[u] += siz[v];
		if(siz[v] > siz[son[u]])
			son[u] = v;
	}
}

void cal(int u,int fa,int val){//统计答案的函数
	int tmp = vis[u-1] + vis[u+1];
	if(val == 1){//插入更新信息
		if(tmp == 0) sum++;
		else if(tmp == 2) sum--;
	}
	else{//消除之前的信息
		if(tmp == 0) sum--;
		else if(tmp == 2) sum++;
	}
	vis[u] = val;
	for(int i = head[u];i;i = edge[i].next){
		int v = edge[i].to;
		if(v == fa || v == flag) continue;//统计答案时不去遍历重儿子 不要写成v == fa || v == son[u] 这样会遗漏答案
		cal(v,u,val);
	}
}

void dfs(int u,int fa,int keep){//先递归一遍轻儿子
	for(int i = head[u];i;i = edge[i].next){
		int v = edge[i].to;
		if(v == fa || v == son[u]) continue;
		dfs(v,u,0);
	}
	if(son[u]){//再递归一遍重儿子
		dfs(son[u],u,1);
		flag = son[u];
	}
	cal(u,fa,1);
	flag = 0;//统计忘了 重儿子标记可以去掉了
	ans[u] = sum;//赋答案
	if(!keep){//需要消除影响的消除一下
		cal(u,fa,0);
	}
}

int main()
{
	int t,cas = 0;
	scanf("%d",&t);
	while(t--){
		int n;
		scanf("%d",&n);
		memset(head,0,sizeof(head));
		int a,b;
		for(int i = 1;i < n;i ++){
			scanf("%d%d",&a,&b);
			addedge(a,b);
			addedge(b,a);
		}
		sum = tot = 0;
		memset(son,0,sizeof(son));
		memset(vis,0,sizeof(vis));
		pre_dfs(1,0);
		dfs(1,0,0);
		printf("Case #%d: ",++cas);
		for(int i = 1;i <= n;i ++) printf(i == n?"%d\n":"%d ",ans[i]);
	}
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值