Codeforces Round #397 by Kaspersky Lab and Barcelona Bootcamp (Div. 1 + Div. 2 combined) E

44 篇文章 0 订阅
41 篇文章 0 订阅

题意:

Vanya现在有一棵树,他想把这棵树缩小一些,因此,在合法的情形下他可以无限做这样的操作:任意选定一个节点x为根,如果这时候,x的其中两个子树都是链的情形并且长度相等,那么Vanya可以把其中一条链合并到另一条链上,或者说,直接删去其中一条链,问这样最后最少剩下多少条边?


solution:

不妨先看一下这个问题的反问题,初始给你一条链,可以选择某个点为根,在它的子树中,如果有某个子树是一条链的情形,可以选择把它复制一遍再给这个根当子树,问最后有没有办法生成给出的树

首先可以确定的是,一定存在至少一个节点,使得每次翻倍操作中它都不属于任意一条链,因为如果不存在,那么必有一次翻倍操作使得该点为根的子树不是一条链。这样的点可能有多个,不妨任取一个让它作最后生成树的根。这样这棵树有一些性质,比如它的任意一个子树内所有叶子的深度相同,因为翻倍操作是不影响叶子深度的。然后,它的所有叶子内可能存在的权值不超过两种,因为有三种及以上显然就构造不出来了。

假如能找到所有可能作为根的点再check一下这条链就找到了

不过有个很方便的确定方案,找出树的任意一条直径,直径中间的那个点就是可能为根的点

还是想象一开始的那条链,如果确定为根的那个点本身就在链中间,那么 ,因为翻倍操作不影响深度,所以最后生成的一定是一棵每个叶子都具有相同深度的树,根就是直径中点了

如果确定为根的点并不在链中间,那么它肯定偏向链的某一侧,这样,另一侧的深度就会偏长,只要翻倍一下,最后确定直径的时候一定就选到这个点了

因此先确定后用dfs check一下就能判断能否生成,若能,就确定了初始链

最后,初始链是可以继续缩短的,特判一下(长度每次可能可以除以2来着...)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;

const int maxn = 2E5 + 20;

int n,s,t,tp,L[maxn],from[maxn],dis[maxn],rd[maxn];
bool vis[maxn],bo[maxn];

vector <int> v[maxn];
queue <int> Q;

int Dfs(int x,int from)
{
	int ret = 0;
	for (int i = 0; i < v[x].size(); i++)
	{
		int to = v[x][i];
		if (to == from) continue;
		L[to] = L[x] + 1;
		int now = Dfs(to,x);
		if (now == -1) return -1;
		if (!ret) ret = now;
		else if (ret != now) return -1;
	}
	return ret ? ret : L[x];
}

bool Check(int x)
{
	int A,B,cnt = 0; memset(bo,0,sizeof(bo));
	for (int i = 0; i < v[x].size(); i++)
	{
		int to = v[x][i],ret;
		L[to] = 1; ret = Dfs(to,x);
		if (ret == -1) return 0;
		if (bo[ret]) continue;
		++cnt; bo[ret] = 1;
		if (cnt == 1) A = ret;
		else if (cnt == 2) B = ret;
		else return 0;
	}
	if (cnt == 1)
	{
		while (!(A & 1)) A >>= 1;
		cout << A << endl; exit(0);
	}
	else
	{
		A += B; while (!(A & 1)) A >>= 1;
		cout << A << endl; exit(0);
	}
}

void BFS(int x)
{
	memset(vis,0,sizeof(vis));
	dis[x] = 0; Q.push(x); vis[x] = 1;
	while (!Q.empty())
	{
		int k = Q.front(); Q.pop();
		for (int i = 0; i < v[k].size(); i++)
		{
			int to = v[k][i];
			if (vis[to]) continue;
			vis[to] = 1; dis[to] = dis[k] + 1;
			from[to] = k; Q.push(to);
		}
	}
}

int main()
{
	#ifdef DMC
		freopen("DMC.txt","r",stdin);
	#endif
	
	cin >> n;
	for (int i = 1; i < n; i++)
	{
		int x,y; scanf("%d%d",&x,&y);
		v[x].push_back(y);
		v[y].push_back(x);
	}
	BFS(1); int Max = 0;
	for (int i = 2; i <= n; i++)
		if (dis[i] > Max) Max = dis[i],s = i;
	BFS(s); from[s] = Max = 0;
	for (int i = 1; i <= n; i++)
		if (dis[i] > Max) Max = dis[i],t = i;
	for (int x = t; x; x = from[x]) rd[++tp] = x;
	int mid = 1 + tp >> 1;
	if (tp & 1) Check(rd[mid]);
	else Check(rd[mid]),Check(rd[mid + 1]);
	cout << -1 << endl;
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值