[ARC152F] Attraction on Tree
Description
你有一棵有 个点的树。一开始,树上的 1 号节点处有一个卡片。
你需要进行以下操作恰好 次:
- 选择一个之前没有被选择过的点,将卡片向那个点移动一条边。不能选择恰好在卡片位置的点
称一个选择点的顺序是 good 的,当且仅当 次操作后卡片在 号节点。
你需要回答,一个 good 的顺序在过程中卡片最少访问了多少个节点。或者报告不存在 good 的顺序。
Solution
观察一下这个过程可以发现 的路径上的点必然会被经过。想一想有没有什么方法能只让这些点被经过。
那么你就需要让它在 的路径上相邻的点 上反复横跳,这就需要在 这侧选一个点、再在 这侧选一个点来实现。
形式化地,假设路径是 , 可支配的子树大小是 (建议意会这个“ 可支配的点”,或者说把树看成羊肉串上的 坨, 就是那坨的大小)
这时候我们就需要每个 满足 。这个式子本身结合经典贪心模型并不难理解,但是可能的一个疑问就是 每个点 都支出了它能支配的 个点,这合理吗?
本质上是出现多个点 满足 。首先发现这个 ,于是这两个可以互相抵消掉 得到合法状态。
如果出现了 那么必然需要经过这个子树里面的节点。如果 的子孙 的可支配点超限了(),则必然向 方向移动。此时问题可以转化为把 的可支配点分成若干连通块,每个连通块都不超限。这时候你又发现分割的连通块数量很有限了(只有一个链的 不合法),沿着重儿子把它找到即可。
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律