[JOI2019 Final] 独特的城市 题解
好题当赏!
容易证明:一个点的独特城市一定在从这个点出发的最长链上,而树上距离点 \(i\) 最远的点,一定是树的直径的两个端点之一。
于是,我们找出一条树的直径,并从它的两个端点各做一次 \(dfs\),企图找到最优解。在 \(dfs\) 时,我们默认此时作为根节点的 \(rt\) 距离所有点最远。显然,两次 \(dfs\) 中,每个点的答案都至少有一个 \(0\),所以答案即为两次计算结果中非 \(0\) 的那个。
我们尝试构造一种通过维护单调栈求解的方案,如下(下设每个点子树内部最长链长度为 \(mx_i\),次长链长度为 \(nx_i\),长儿子为 \(sn_i\),每个点深度为 \(d_i\)):
-
有父亲的话插入父亲。
-
将单调栈中与当前点 \(u\) 距离 \(\le nx_u\) 的点弹出,同时修改答案。
此时,我们构建出的单调栈是对于长儿子的,所以选择次长链(所以该问题中,次长链不经过长儿子)。 -
遍历长儿子。
-
将单调栈中与当前点 \(u\) 距离 \(\le mx_u\) 的点弹出,同时修改答案。
此时就是对于 \(u\) 和短儿子的状态了。 -
此时的答案就是该次 \(dfs\) 中 \(u\) 的答案,记录。
-
遍历其他短儿子。
-
若父亲还活着,弹出父亲。
时间复杂度 \(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,m,tp,st[N],tc[N],ans[N];
int tl,tr,c[N],sn[N],d[N],mx[N];
vector<int>g[N];int nx[N],nw;
int dfs(int x,int fa){
d[x]=d[fa]+1;int re=x;
for(auto y:g[x]) if(y!=fa){
int as=dfs(y,x);
if(d[as]>d[re]) re=as;
}return re;
}void dfs2(int x,int fa){
mx[x]=d[x]=d[fa]+1,nx[x]=sn[x]=0;
for(auto y:g[x]) if(y!=fa){
dfs2(y,x);if(mx[x]<mx[y])
nx[x]=mx[x],sn[x]=y,mx[x]=mx[y];
else nx[x]=max(nx[x],mx[y]);
}
}void dfs3(int x,int fa){
if(fa) nw+=(tc[c[st[++tp]=fa]]++==0);
while(tp&&d[x]*2<=nx[x]+d[st[tp]])
nw-=(--tc[c[st[tp--]]]==0);
if(sn[x]) dfs3(sn[x],x);
while(tp&&d[x]*2<=mx[x]+d[st[tp]])
nw-=(--tc[c[st[tp--]]]==0);
ans[x]=max(ans[x],nw);
for(auto y:g[x]) if(y!=fa&&y!=sn[x]) dfs3(y,x);
if(fa&&st[tp]==fa) nw-=(--tc[c[st[tp--]]]==0);
}int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0),cin>>n>>m;
for(int i=1,u,v;i<n;i++)
cin>>u>>v,g[u].push_back(v),g[v].push_back(u);
for(int i=1;i<=n;i++) cin>>c[i];
tl=dfs(1,0),dfs2(tr=dfs(tl,0),0);
dfs3(tr,0),dfs2(tl,0),dfs3(tl,0);
for(int i=1;i<=n;i++) cout<<ans[i]<<"\n";
return 0;
}