[ARC152F] Attraction on Tree

Description

你有一棵有 N 个点的树。一开始,树上的 1 号节点处有一个卡片。

你需要进行以下操作恰好 N 次:

  • 选择一个之前没有被选择过的点,将卡片向那个点移动一条边。不能选择恰好在卡片位置的点

称一个选择点的顺序是 good 的,当且仅当 N 次操作后卡片在 N 号节点。

你需要回答,一个 good 的顺序在过程中卡片最少访问了多少个节点。或者报告不存在 good 的顺序。

N200000

Solution

观察一下这个过程可以发现 1n 的路径上的点必然会被经过。想一想有没有什么方法能只让这些点被经过。

那么你就需要让它在 1n 的路径上相邻的点 x,y 上反复横跳,这就需要在 x 这侧选一个点、再在 y 这侧选一个点来实现。

形式化地,假设路径是 (p0=1,pm=n)pt 可支配的子树大小是 sizt(建议意会这个“pt 可支配的点”,或者说把树看成羊肉串上的 m+1 坨,sizt 就是那坨的大小)

这时候我们就需要每个 t 满足 nm2(siztt) 。这个式子本身结合经典贪心模型并不难理解,但是可能的一个疑问就是 每个点 pt 都支出了它能支配的 t 个点,这合理吗?

本质上是出现多个点 s1,sk 满足 2(sizsiri)nm<2sizsi,risi。首先发现这个 k2,于是这两个可以互相抵消掉 min(r1,r2) 得到合法状态。

如果出现了 2(siztt)>nm 那么必然需要经过这个子树里面的节点。如果 pt 的子孙 q 的可支配点超限了(sizq>nm2+t),则必然向 q 方向移动。此时问题可以转化为把 pt 的可支配点分成若干连通块,每个连通块都不超限。这时候你又发现分割的连通块数量很有限了(只有一个链的 siz 不合法),沿着重儿子把它找到即可。

Code

const int N=2e5+10;
vector<int> G[N];
int n,dep[N],fa[N],siz[N];
inline void dfs(int x,int fat){
	siz[x]=1; fa[x]=fat;
	for(auto t:G[x]) if(t!=fat){
		dep[t]=dep[x]+1;
		dfs(t,x);
		siz[x]+=siz[t];
	}
	return ;
}
int ans,mark[N];
inline void solve(int x,int lim){
	++ans; mark[x]=1;
	sort(G[x].begin(),G[x].end(),[&](const int x,const int y){return siz[x]>siz[y];});
	for(auto t:G[x]) if(t!=fa[x]&&!mark[t]){
		if(siz[x]<=lim) break;
		siz[x]-=siz[t];
		solve(t,lim);
	}
	return ;
}
int main(){
	n=read();
	for(int i=1;i<n;++i){
		int u=read(),v=read();
		G[u].emplace_back(v);
		G[v].emplace_back(u);
	}	
	dfs(1,0);
	if(dep[n]%2!=n%2) puts("-1"),exit(0);
	int x=n;
	vector<int> nds;
	while(x){
		mark[x]=1;
		nds.emplace_back(x);
		x=fa[x];
	}
	reverse(nds.begin(),nds.end());
	for(auto x:nds) siz[fa[x]]-=siz[x];
	reverse(nds.begin(),nds.end());
	for(auto x:nds) solve(x,(n-dep[n])/2+dep[x]);
	cout<<ans<<endl;
	return 0;
}
posted @   没学完四大礼包不改名  阅读(38)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示