1727 洪水拯救计划

Task
一棵n个节点的树,给定K个点。
求从每个节点(1到n)出发,确定一个遍历顺序,求遍历完K个点不返回原点的最小距离。
N<=5e5,K<=n,边权<=1e6

Solution
普遍化问题:
① 给定一棵树,从根出发,要求遍历每个叶子节点,最后回到根,求最小距离?

距离由每条边贡献,用反证法证明每条边至少遍历2次(一上一下)。
最小距离=2*边权总和。

② 给定一棵树,从根出发,要求遍历每个叶子节点,不用回到根,求最小距离?

最后一个点x不用回到根,那么x到根路径上所有的边,都至少遍历一遍(只下不上)。
最小距离=2*边权总和-dis[x].
x为K点中根距最大的点。

回到这个问题。
如果从x出发,以x为根建树,那么K点等价于该子树的叶子节点,可以列出x值的表达式。
若对于每个x,dfs找解会超时。是否可以找到所有询问中的共性呢?

对x进行分类:在K子树上,不在K子树上。
如果不在K子树上,权值总和=K子树的权值和*2+链接K子树的边权*2。
如果在K子树上,链接值=0
因此所有的答案都是以K子树的权值为基础,再找到链接值。
列出表达式:Ans[x]=vk*2+链接值*2-dis[x]
Vk表示K子树边权和,链接值表示x到祖先上第一个属于K子树的点的距离,dis[x]表示到K子树所有点中最大距离。

列出了表达值后,只要维护关键信息就好了。
X到K子树节点中的最大值,符合“树的直径”的性质,该节点必定是K树的两个端点之一。

当然,最好自己画个图模拟一下。

const int M=5e5+3;
int head[M],ID[2];
ll dis[M];
bool vis[M];
int n,m,ecnt,rt,num;
ll vtot,dmx;//权值和,直径 
struct edge{
    int t,v,nxt;
}e[M<<1];
inline void addedge(int f,int t,int v){
    e[++ecnt]=(edge){t,v,head[f]};
    head[f]=ecnt;
}
inline void input(){
    int i,j,k,a,b,c;
    rd(n);rd(m);
    rep(i,1,n-1){
        rd(a);rd(b);rd(c);
        addedge(a,b,c);
        addedge(b,a,c);
    }
    rep(i,1,m){
        rd(rt);
        vis[rt]=1;//标记是K树上的点 
    }
}
inline void build(int f,int x){//建K树,得权值和 
    tral(i,x){
        if(e[i].t==f)continue;
        build(x,e[i].t);
        if(vis[e[i].t]){
            vis[x]=1;
            vtot+=e[i].v*2;
        }
    }
}
inline void find_D(int f,int x,ll d){
    if(vis[x]&&d>dmx){
        ID[num]=x;
        dmx=d;
    }
    dis[x]=max(dis[x],d);
    tral(i,x){
        if(e[i].t==f)continue;
        find_D(x,e[i].t,d+e[i].v);
    }
}
inline void find_in(int f,int x,int d){//到K子树的入口 
    if(vis[x])d=0;
    dis[x]=2*d-dis[x];
    tral(i,x){
        if(e[i].t==f)continue;
        find_in(x,e[i].t,d+e[i].v);
    }
}
inline void init(){
    build(0,rt);
    find_D(0,rt,0);
    dmx=0;num++;
    find_D(0,ID[0],0);
    find_in(0,ID[1],0);

}
inline void print(){
    rep(i,1,n)sc(vtot+dis[i]);
}
int main(){
    input();
    init();
    print();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值