AGC009D Uninity 做题记录

如下定义一棵树的海胆度:

  • 一个单点的海胆度是 00
  • 一棵树的海胆度是最小的 kk 满足删掉其某个节点后剩下的连通块的海胆度都 k1\le k-1

给定一棵 nn 个点的树,求它的海胆度。

1n1051\le n\le 10^5

相当于找到最小的颜色数 kk,使得给每个点染 [1,k][1,k] 中的颜色,要求任意两个颜色均为 xx 的点之间路径上颜色的最大值 >x>x

考虑这样一个不是很显然的贪心:

  • 随便定一个根,自底向上确定每个点的颜色:
    • 对于点 uu,其颜色确定为只考虑其子树时的最小的合法颜色;

具体的,每个点维护一个集合 banuban_u,表示 uu 的子树内还未被覆盖的颜色,即所有满足存在 colv=xcol_v=xvuv\to u 链上不存在颜色 >x>x 的点的 xx 组成的集合。

那么 uu 儿子的 banbancolucol_u 仅有如下限制:

  1. vsonu\forall v\in son_ucolubanvcol_u\not\in ban_v
  2. v1,v2sonu,v1=v2\forall v_1,v_2\in son_u,v_1\not=v_2colu>maxx(banv1banv2)xcol_u>\max\limits_{x\in \left(ban_{v_1}\cap ban_{v_2}\right)} x

并且确定了 colucol_u 之后就可以确定 banu={colu}{xx>colu,vsonu,xbanv}ban_u=\{col_u\}\cup\{x|x> col_u,\exist v\in son_u,x\in ban_v\},即所有儿子的 banban 的并加上 colucol_u,再清除 <colu<col_u 的元素。

而显然 klognk\le \log n,所以可以用二进制记录 banban,使用 __builtin 的位运算即可做到 O(n)O(n)

贪心证明

取一个巨大的位权 BB。对于一个集合 SS,设 V(S)=xSBxV(S)=\sum\limits_{x\in S}B^x

那么目标是令 maxuV(banu)\max\limits_uV(ban_u) 尽可能小,限制相当于要求 V(banu)>vsonuV(banv)V(ban_u)>\sum\limits_{v\in son_u}V(ban_v),即若某一位的系数 2\ge 2 就需要在更高位填 11

由于我们只能在 vsonuV(banv)\sum\limits_{v\in son_u}V(ban_v) 的为 00 的那些位中填一个 11 并将后面的所有位置为 00,故这样贪心可以使得 V(banu)V(ban_u) 最小。

相当于证明了一个大颜色对子树外的影响大于任意个小颜色对子树外的影响。

代码如下:

#include <iostream>
#include <cstdio>
#include <vector>

using namespace std;

const int S=100005;

int n;
vector<int> g[S];
int f[S],ans;

void dfs(int u,int fa)
{
	int d=0;
	for(int v:g[u]) d+=(v!=fa);
	if(d==0) f[u]=1;
	else
	{
		int ban=0,big=0;
		for(int v:g[u])
			if(v!=fa)
			{
				dfs(v,u);
				big|=ban&f[v];
				ban|=f[v];
			}
		int t;
		if(big==0) t=0;
		else t=32-__builtin_clz(big);
		int tmp=ban;
		tmp>>=t;
		tmp^=(1<<20)-1;
		int res=__builtin_ctz(tmp)+t;
		ans=max(ans,res);
		f[u]=((1<<res)|ban)>>res<<res;
	}
}

int main()
{
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n-1;i++)
	{
		int x,y;
		cin>>x>>y;
		g[x].push_back(y);
		g[y].push_back(x);
	}
	dfs(1,0);
	cout<<ans<<'\n';
	return 0;
}