LGP4103 [HETS 2014] 大工程 学习笔记

LGP4103 [HETS 2014] 大工程 学习笔记

Luogu Link

题意简述

一棵 \(n\) 个结点的树,每条边权值都为 \(1\)

\(m\) 次询问,每次给定 \(k_i\) 个关键点,问对于这所有关键点两两相连的所有简单路径,

  1. 长度总和。
  2. 最小长度。
  3. 最大长度。

\(n\le 10^6\)\(\sum k_i\le 2\times n\)

做法解析

显然这个 \(\sum k_i\le 2\times n\) 就差把虚树两个字写题面上了。建虚树就不赘述了。这里讲一下 \(\texttt{DP}\) 部分。

第一问是我想的最久的一问,我们维护每个子树里的关键点数量 \(siz\) 和所有关键点往上延伸的线头长度之和 \(lsum\),然后转移差不多长成这样:\(dp_u=\sum dp_v+lsum_u*siz_v+lsum_v*siz_u\),其中一个子树计算完成时就将其信息合并进 \(u\)

第二问是最水的一问。我们对于每个点维护它子树内所涉及到的最浅深度,求长度的 \(\min\) 显然就是每次将两棵子树的最浅深度拼起来,然后如果 \(u\) 是关键点就尝试和 \(u\) 拼一下。

第三问和第二问原理是一样的。不过有一点得注意,详见下文。

代码实现

注意看到 mxd[u]=-Inf,这里我们需要将它初始化为负无穷。是的,初始化为 \(0\) 是不够的。你考虑这样一个情景:原树上只有两个关键点且其 \(\text{lca}\) 就是它们共同的父亲。这种情况下最长链长应该是 \(2\),然而如果 mxd[u]=0,就会因为 maxxer(dp3[u],max(dp3[v],mxd[u]+mxd[v]-2*dep[u])); 而凭空转移出一个 \(0+dep_v-2\times 1=dep_v-2\) 的虚空答案,而这是不合法的。

#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=1e6+5,Inf=0x3f3f3f3f;
int N,X,Y,M,K;
vector<int> Tr[MaxN];
void addudge(int u,int v){
    Tr[u].push_back(v);
    Tr[v].push_back(u);
}
int dep[MaxN],tfa[MaxN],tsiz[MaxN],hvs[MaxN];
void dfs1(int u,int f){
    dep[u]=dep[f]+1,tfa[u]=f,tsiz[u]=1;
    for(int v : Tr[u]){
        if(v==f)continue;
        dfs1(v,u),tsiz[u]+=tsiz[v];
        if(tsiz[v]>tsiz[hvs[u]])hvs[u]=v;
    }
}
int dfn[MaxN],dcnt,top[MaxN];
void dfs2(int u,int t){
    dfn[u]=++dcnt,top[u]=t;if(hvs[u])dfs2(hvs[u],t);
    for(int v : Tr[u])if(v!=tfa[u]&&v!=hvs[u])dfs2(v,v);
}
int getlca(int x,int y){
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        x=tfa[top[x]];
    }
    return dep[x]<=dep[y]?x:y;
}
vector<int> Vr[MaxN];
int P[MaxN],isk[MaxN],stk[MaxN],ktp;
bool cmpd(int x,int y){return dfn[x]<dfn[y];}
void addedge(int u,int v){Vr[u].push_back(v);}
void vtbuild(int k){
    sort(P+1,P+k+1,cmpd),stk[ktp=1]=1;
    for(int i=1;i<=k;i++){
        if(P[i]==1)continue;int ca=getlca(P[i],stk[ktp]);
        if(ca!=stk[ktp]){
            while(dfn[ca]<dfn[stk[ktp-1]])addedge(stk[ktp-1],stk[ktp]),ktp--;
            if(dfn[ca]!=dfn[stk[ktp-1]])addedge(ca,stk[ktp]),stk[ktp]=ca;
            else addedge(ca,stk[ktp--]);
        }
        stk[++ktp]=P[i];
    }
    for(int i=1;i<ktp;i++)addedge(stk[i],stk[i+1]);
}
int vsiz[MaxN],mxd[MaxN],mnd[MaxN];
lolo dp1[MaxN],lsum[MaxN];int dp2[MaxN],dp3[MaxN];
void DP(int u){
    dp1[u]=dp3[u]=0,dp2[u]=Inf;
    vsiz[u]=lsum[u]=0,mxd[u]=-Inf,mnd[u]=Inf;
    for(int v : Vr[u]){
        DP(v),lsum[v]+=vsiz[v]*(dep[v]-dep[u]);
        dp1[u]+=dp1[v]+lsum[u]*vsiz[v]+lsum[v]*vsiz[u];
        vsiz[u]+=vsiz[v],lsum[u]+=lsum[v];
        minner(dp2[u],min(dp2[v],mnd[u]+mnd[v]-2*dep[u]));
        maxxer(dp3[u],max(dp3[v],mxd[u]+mxd[v]-2*dep[u]));
        maxxer(mxd[u],mxd[v]),minner(mnd[u],mnd[v]);
    }
    if(isk[u]){
        dp1[u]+=lsum[u],vsiz[u]++;
        maxxer(dp3[u],mxd[u]-dep[u]);
        minner(dp2[u],mnd[u]-dep[u]);
        maxxer(mxd[u],dep[u]),minner(mnd[u],dep[u]);
    }
    Vr[u].clear();
}
int main(){
    readi(N);
    for(int i=1;i<N;i++)readis(X,Y),addudge(X,Y);
    dfs1(1,0),dfs2(1,1),readi(M);
    for(int i=1;i<=M;i++){
        readi(K);
        for(int j=1;j<=K;j++)readi(P[j]),isk[P[j]]=1;
        vtbuild(K),DP(1);writip(dp1[1]),writip(dp2[1]),writil(dp3[1]);
        for(int j=1;j<=K;j++)isk[P[j]]=0;
    }
    return 0;
}

反思总结

注意,如果一个最大值的初值是“不存在”,你可能需要将它设为 \(-\infty\) 而不仅仅是 \(0\)

posted @ 2025-07-31 13:24  矞龙OrinLoong  阅读(8)  评论(0)    收藏  举报