[ARC152F] Attraction on Tree

Description

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

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

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

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

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

\(N\le 200000\)

Solution

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

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

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

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

本质上是出现多个点 \(s_1,\dots s_k\) 满足 \(2(siz_{s_i}-r_i)\le n-m< 2 siz_{s_i},r_i\le s_i\)。首先发现这个 \(k\le 2\),于是这两个可以互相抵消掉 \(\min(r_1,r_2)\) 得到合法状态。

如果出现了 \(2(siz_t-t)>n-m\) 那么必然需要经过这个子树里面的节点。如果 \(p_t\) 的子孙 \(q\) 的可支配点超限了(\(siz_q>\frac {n-m}2+t\)),则必然向 \(q\) 方向移动。此时问题可以转化为把 \(p_t\) 的可支配点分成若干连通块,每个连通块都不超限。这时候你又发现分割的连通块数量很有限了(只有一个链的 \(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 @ 2023-11-12 17:57  yspm  阅读(50)  评论(0)    收藏  举报