Logo Iceturky 的博客

博客

P4186 [USACO18JAN] Cow at Large G题解

...
Iceturky
2025-12-01 12:54:34
星屑落ちて 華は散っても キラめく舞台に 生まれて変わる

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-11-08 19:01:57

Link

贪心神仙题。

注意「割」字眼在文中的实际意义并非割点/割边,而是割集

考虑把在放满人情况下,人只往上走,Bessie 只往下走情况下,人抓到 Bessie 的点/边称为相遇点/边,选择其中所有 没有祖宗是相遇点/边 的相遇点/边,作为根到叶子的割集。(这里不需要纠结边和点在割中的区别,可以看作在每条边中间都放了一个中间点来将边转化为点,以下直接简称为相遇点)

那么我们会发现答案就是割的大小。

考虑证明。

首先其一定存在,这个显然。

割集内的每一个点都可以不唯一但不重复的对应到一个叶子,这里不唯一是没有关系的,不重复是较重要的。这意味着我们可以用割来对应题目的解——选择的叶子集合。以下对于答案的讨论都是对于割集的。选择割集所对应的叶子集合是一定合法的。

接下来考虑是否最优。

将一个人的路径是所在叶子到根的唯一路径,因为反过来追是一定追不上的。

非相遇点是无用的,因为这些点要么是人在不错过 Bessie 时到达不了的点(割上面的点),要么是可以被割中点所替代的(割下面的点)。

在割下面(更靠近叶子)的相遇点是无用的,可以被割中点替代。

根据选取规则,割上面没有相遇点。

所以由割集得出的答案叶子集合是一个存在,合法且最优的答案集合。

(感觉证得好混乱,但感性理解是非常容易的)

代码实现则非常简单,只需要预处理一下每一棵子树的最浅叶子到自身距离,求解则直接 dfs 找割即可。

code

const int N=2e5+5;

struct edge{
	int v,nxt;
}e[N<<1];
int head[N],tott;
int in[N];
void add(int u,int v){e[++tott]={v,head[u]},head[u]=tott;in[u]++;}

int len[N];
void dfs(int u,int faa){
	len[u]=in[u]==1?0:inf;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==faa)
			continue;
		dfs(v,u);
		len[u]=min(len[u],len[v]+1);
	}
}

int cnt;
void calc(int u,int faa,int dep){
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==faa)
			continue;
		if(len[v]<=dep+1)\/\/儿子是割点\/到儿子的边是割边
			cnt++;
		else
			calc(v,u,dep+1);
	}
}

signed main(){
	int n=read(),k=read();
	for(int i=1;i<n;i++){
		int u=read(),v=read();
		add(u,v),add(v,u);
	}
	dfs(k,0);
	calc(k,0,0);
	print(cnt),pc('\n');
	return 0;
}

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。