Treediff

一、题目

题目描述

n n n个点的树,有 m m m个叶子结点,只有叶子结点有权值,问每一个非叶节点的子树内选两个不同的叶子的最小差,如果不存在输出 2 31 − 1 2^{31}-1 2311

数据范围

1 ≤ m < n ≤ 50000 1\leq m<n\leq 50000 1m<n50000

二、解法

启发式合并的版题,随便取一个子树的 s e t set set,然后启发式合并,把小的并到大的那里去,随便算答案就行了。

证明一下这样做的复杂度,每次合并后的数量一定是小的集合大小的两倍,最开始的大小都为 1 1 1,合并 t t t次后大小就是 2 t 2^t 2t,故每个点只会合并 t = log ⁡ t=\log t=log次,总复杂度就是 O ( n log ⁡ 2 n ) O(n\log ^2n) O(nlog2n)

#include <cstdio>
#include <iostream>
#include <set>
using namespace std;
const int M = 50005;
int read()
{
    int num=0,flag=1;char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
    while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
    return num*flag;
}
int n,m,tot,f[M],b[M],res[M];
set<int> a[M];
struct edge
{
	int v,next;
}e[2*M];
int dfs(int u,int fa)
{
	int ans=2147483647;
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==fa) continue;
		ans=min(ans,dfs(v,u));
		if(!b[u]) {b[u]=b[v];continue;}
		int len1=a[b[u]].size(),len2=a[b[v]].size();
		set<int>::iterator it,now;
		if(len1>len2)
		{
			for(it=a[b[v]].begin();it!=a[b[v]].end();it++)
			{
				now=a[b[u]].lower_bound(*it);
				if(now!=a[b[u]].end()) ans=min(ans,(*now)-(*it));
				if(now!=a[b[u]].begin()) now--,ans=min(ans,(*it)-(*now));
				a[b[u]].insert(*it);
			}
		}
		else
		{
			for(it=a[b[u]].begin();it!=a[b[u]].end();it++)
			{
				now=a[b[v]].lower_bound(*it);
				if(now!=a[b[v]].end()) ans=min(ans,(*now)-(*it));
				if(now!=a[b[v]].begin()) now--,ans=min(ans,(*it)-(*now));
				a[b[v]].insert(*it);
			}
			b[u]=b[v];
		}
	}
	return res[u]=ans;
}
int main()
{
	n=read();m=read();
	for(int i=2;i<=n;i++)
	{
		int j=read();
		e[++tot]=edge{i,f[j]},f[j]=tot;
		e[++tot]=edge{j,f[i]},f[i]=tot;
	}
	for(int i=n-m+1;i<=n;i++)
		a[i].insert(read()),b[i]=i;
	dfs(1,0);
	for(int i=1;i<=n-m;i++)
		printf("%d ",res[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值