题解 1654G Snowy Mountain
1654G Snowy Mountain
一棵
n(n\le 2\cdot 10^5) 个点的树,布尔数组l_i 表示点i 是不是基地,一个点的高度h_i 为其到最近的基地的距离。从一个点出发时动能为0 ,随后可以下滑或走平地,从u 到相邻的v :若h_u>h_v ,则增加1 动能,若h_u=h_v ,则减少1 动能。问从每个点出发、全程动能非负最远可以到的距离。
滑雪下来重力势能做的功是恒定的,我们实际要最大化产生的热能,即滑更多的平地。
结论1(贪心)最优策略为滑到一个海拔最低的“辗转点”,再在这个平台上辗转直到动能耗尽,再由它径直滑到最近的基地。定义“辗转点”为相邻节点中有与它等高的点的点。
证明:当人到达终点时,具有动能
现在的任务变为求一个点能到达的最低“辗转点”。
结论2 所有“辗转点”的高度总和为
证明:
推论 高度不同的“辗转点”数量不超过
这就是说我们可以暴力枚举辗转平台高度,并看看哪些点可以到达这一高度的辗转点的任意一个,更新它们的答案。
具体地,我们设
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5,INF=0x3f3f3f3f;
int n,l[N],h[N],u[N],v[N],c[N],mn[N];
vector<int>G[N],nd[N],land[N];
queue<int>Q;
inline int read(){
register char ch=getchar();register int x=0;
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x;
}
void bfs(int H){
memset(c,0x3f,sizeof(c));
for(int i=0;i<land[H].size();i++)c[land[H][i]]=0;
for(int i=H;i<=n;i++){
for(int j=0;j<nd[i].size();j++)if(c[nd[i][j]]!=INF)Q.push(nd[i][j]);
while(!Q.empty()){
int x=Q.front();Q.pop();
for(int j=0;j<G[x].size();j++){
int y=G[x][j];
if(h[x]!=h[y])continue;
if(c[x]+1<c[y]){
c[y]=c[x]+1;
Q.push(y);
}
}
}
for(int j=0;j<nd[i].size();j++)if(c[nd[i][j]]!=INF){
int x=nd[i][j];
for(int k=0;k<G[x].size();k++){
int y=G[x][k];
if(h[x]<h[y]&&c[x]-1<c[y]){
c[y]=max(c[x]-1,0);
}
}
}
}
for(int i=1;i<=n;i++)if(!c[i])mn[i]=min(mn[i],H);
}
int main(){
n=read();
memset(h,-1,sizeof(h));
for(int i=1;i<=n;i++){
l[i]=read();
if(l[i])h[i]=0,Q.push(i);
}
for(int i=1;i<n;i++)u[i]=read(),v[i]=read(),G[u[i]].push_back(v[i]),G[v[i]].push_back(u[i]);
while(!Q.empty()){
int x=Q.front();Q.pop();
for(int i=0;i<G[x].size();i++){
int y=G[x][i];
if(h[y]==-1)h[y]=h[x]+1,Q.push(y);
}
}
for(int i=1;i<=n;i++)nd[h[i]].push_back(i);
for(int i=1;i<=n;i++){
bool flag=0;
for(int j=0;j<G[i].size();j++)if(h[G[i][j]]==h[i]){flag=1;break;}
if(flag)land[h[i]].push_back(i);
}
for(int i=1;i<=n;i++)mn[i]=h[i];
for(int i=0;i<=n;i++)if(land[i].size())bfs(i);
for(int i=1;i<=n;i++)cout<<(mn[i]==INF?0:2*h[i]-mn[i])<<' ';
}