CF1914F Programming Competition

Problem - 1914F - Codeforces

  • 我觉得这个题的难度比 \(1900\) 要大吧,感觉细节挺多的

  • 先说一个官方题解的做法

  • 我们从上到下的考虑,对于以 \(u\) 为根时我们把 \(u\) 的所有儿子的 \(siz_x\) 记录下来,让不同儿子的 \(siz_x\) 两两匹配,这里会分成两种情况

    1. 如果 \(siz_{mx} \leq siz_u-1-siz_{mx}\),则我们一定可以把这些合并成 \(\lfloor \frac{siz_u-1}{2} \rfloor\)

    2. 如果 \(siz_{mx} > siz_u-1-siz_{mx}\),那我们在把 \(mx\) 和其他所有儿子合并后依然还剩下 \(siz_{mx}-(siz_u-1-siz_mx)\) 个没有合并,而且都在 \(mx\) 子树里,这时候我们就递归到 \(mx\) 里继续寻找答案

  • 现在的细节就是我们继续寻找答案的时候我们要以什么方式去匹配

  • 答案是优先匹配祖先节点的

  • 考虑如果存在一种最优解是先考虑了 \(mx\) 子树内的匹配再去考虑 \(mx\) 和祖先的匹配

  • 如果祖先还能继续匹配的个数是偶数,那我们把 \(mx\) 的一对匹配拆开匹配给祖先显然等价

  • 如果祖先还能继续匹配的个数为奇数,那我们如果把 \(mx\) 的一对匹配拆开后把一个和祖先匹配,另一个剩下,到达这个祖先节点的父节点时两者没有被匹配的节点数是相同的,也就是说两者匹配数相同,依然是最优解

  • 而且我们变换为先匹配祖先节点的原因是先考虑子孙节点比较“片面”,典型的就是第 \(5\) 个样例,如果先考虑子孙节点就会出现 \((5,6),(2,3)\) 这样的组合,答案容易更劣;而考虑祖孙节点更“整体”

  • 现在我们既然递归到 \(mx\) 里,我们就需要把祖孙节点和他匹配的情况给挑出来。我们在递归进去时记一个 \(K\) 表示 \(mx\) 的祖先节点已经用掉了 \(K\) 个点,因此情况变为:

    1. 如果 \(siz_{mx}-k \leq siz_u-1-siz_{mx}\),则我们一定可以把这些合并成 \(\lfloor \frac{siz_u-1}{2} \rfloor\)

    2. 如果 \(siz_{mx}-k > siz_u-1-siz_{mx}\),那我们在把 \(mx\) 和其他所有儿子合并后依然还剩下 \(siz_{mx}-(siz_u-1-siz_mx)\) 个没有合并,而且都在 \(mx\) 子树里,这时候答案为 \(siz_u-1-siz_mx+solve(mx,\max(k+siz_u-1-siz[mx]-1,0))\)

  • 其中注意的是 \(mx\) 子树内是没有节点和 \(mx\) 自身匹配的,因此当往下递归时要让 \(K\) 减一,不用白不用

  • 最终复杂度为 \(O(n)\)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
template<typename T>T &read(T &x){
	cin>>x;
	return x;
}

const int maxn=2e5+50;

int n;
struct E{int v,nxt;}e[maxn];
int hd[maxn],cnt=1;
int siz[maxn];

void ade(int u,int v){
	e[++cnt]=E{v,hd[u]};
	hd[u]=cnt;
}

void dfs1(int u){
	siz[u]=1;
	for(int i=hd[u];i;i=e[i].nxt){
		int v=e[i].v;
		dfs1(v);
		siz[u]+=siz[v];
	}
}

int solve(int u,int k){
	int mx=-1;
	for(int i=hd[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(mx==-1||siz[v]>siz[mx]){
			mx=v;
		}
	}
	if(siz[u]==1){
		return 0;
	}
	if(siz[mx]-k<=siz[u]-1-siz[mx]){
		return (siz[u]-1-k)/2;
	}
	return siz[u]-1-siz[mx]+solve(mx,max(k+siz[u]-1-siz[mx]-1,0));
}

void mian(int TwT){
	read(n);
	int fa;
	for(int i=2;i<=n;++i){
		read(fa);
		ade(fa,i);
	}
	dfs1(1);
	printf("%d\n",solve(1,0));
}

void init(){
	for(int i=1;i<=n;++i){
		hd[i]=0;
	}
	cnt=1;
}

int main(){
	
	int T=1;
	read(T);
	for(int TwT=1;TwT<=T;++TwT){
		init();
		mian(TwT);
	}
	
	return 0;
}

  • 还有洛谷大佬的第二种方法

  • 由于要求 \((u,v)\) 中 \(u\) 和 \(v\) 必须不互为祖先,容易想到贪心只要选两个叶子节点就行了。

  • 因为如果 \((u,v)\) 不互为祖先,那么选一个 \(u\) 的子节点 \(u_0\)​,\((u_0​,v)\) 满足条件且更优。

  • 会发现,取两个深度最大的节点时,能保证这颗树剩下的节点中深度尽量平衡,也就是能构成祖先的 \((u,v)\) 会更少,故贪心可以那么做。

  • 复杂度会多优先队列带来的一个 \(\log\)

posted @ 2024-03-07 19:44  FOX_konata  阅读(3)  评论(0编辑  收藏  举报