【Codeforces 516D】Drazil and Morning Exercise

树的直径,双指针,并查集

题目大意:

给定一棵带权树(权值为正)。定义 $f_i$ 表示 $i$ 到树上离 $i$ 最远的距离。

有 $q$(非常小)次询问,每次给出 $l$,要求在树上选出一个连通块 $S$,使得:

需要求出这个连通块中最多有多少个结点。

题解:

在树上距离某个点最远的点,一定是直径的一个端点,所以 $f_i$ 可以通过树形 DP 线性求得。

考虑将 $f_i$ 从大到小排序,然后对每次询问的 $l$,我们用双指针扫描,每次尺取最长的一段,判断这段结点能构成的最大连通块即可。

我们用并查集维护连通块大小,每次新插入一个结点的时候更新答案即可。

有一个问题,这个做法是需要删除点的,如何在并查集上删掉一个点呢?

我们每次删掉的结点 $i$,一定满足 $f_i$ 是当前区间最大的。那么与它相连的点只可能有一个。

也就是说,我们删掉这个点,不会改变其他结点之间的连通性,只会使其所在连通块大小减一。而且之后的操作都和这个点无关。

那么删除的时候只需要将其所在连通块大小减一,别的什么都不用干。

时间复杂度 $O(n\log n+nq\alpha(n))$。

Code:

#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=1e5+5;
int n,head[N],cnt,m_id[N],u,v,id[N];
LL mx[N],f[N],Len,Dist[N];
int fa[N],sz[N];
bool vis[N];
inline int find(int x){while(x!=fa[x])x=fa[x]=fa[fa[x]];return x;}
struct edge{
    int to,nxt,dis;
}e[N<<1];
inline bool cmp(int a,int b){return f[a]>f[b];}
void DFS(int now,int pre){
    mx[now]=0,m_id[now]=now;
    for(int i=head[now];i;i=e[i].nxt)
    if(e[i].to!=pre){
        DFS(e[i].to,now);
        if(mx[now]+mx[e[i].to]+e[i].dis>Len){
            Len=mx[now]+mx[e[i].to]+e[i].dis;
            u=m_id[now],v=m_id[e[i].to];
        }
        if(mx[now]<mx[e[i].to]+e[i].dis){
            mx[now]=mx[e[i].to]+e[i].dis;
            m_id[now]=m_id[e[i].to];
        }
    }
}
void DfS(int now,int pre){
    f[now]=max(f[now],Dist[now]);
    for(int i=head[now];i;i=e[i].nxt)
    if(e[i].to!=pre){
        Dist[e[i].to]=Dist[now]+e[i].dis;
        DfS(e[i].to,now);
    }
}
int ans;
inline void merge(int x,int y){
    x=find(x),y=find(y);
    if(sz[x]<sz[y])swap(x,y);
    ans=max(ans,sz[x]+=sz[y]),fa[y]=x;
}
void add(int x){
    for(int i=head[x];i;i=e[i].nxt)
    if(vis[e[i].to])merge(x,e[i].to);
    vis[x]=1;
}
inline void erase(int x){
    vis[x]=0;
    --sz[find(x)];
}
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<n;++i){
        int u,v,w;
        cin>>u>>v>>w;
        e[++cnt]=(edge){v,head[u],w},head[u]=cnt;
        e[++cnt]=(edge){u,head[v],w},head[v]=cnt;
    }
    DFS(1,0);
    DfS(u,Dist[u]=0);DfS(v,Dist[v]=0);
    for(int i=1;i<=n;++i)id[i]=i;
    sort(id+1,id+n+1,cmp);
    int q;
    for(cin>>q;q--;){
        LL len;
        cin>>len;
        if(f[id[1]]-f[id[n]]<=len){
            cout<<n<<'\n';
            continue;
        }
        ans=1;
        for(int i=1;i<=n;++i)fa[i]=i,sz[i]=1,vis[i]=0;
        for(int l=1,r=1;l<=n&&r<=n;++l){
            while(r<=n&&f[id[l]]-f[id[r]]<=len)add(id[r++]);
            erase(id[l]);
        }
        cout<<ans<<'\n';
    }
    return 0;
}