hdu5296

题意:给定一颗边权树,动态地在点集中增加和删除点。求当前所在点集中使得所有点都连通的最小边权和。
解法:
首先介绍一个结论:
树上点u到以x和y为端点的链的最段距离可以在O(n)预处理和O(logn)时间复杂度内求出。
证明:设z=lca(x,y),dis[u]为树中点u到跟节点的路径权值和。则dis< u,(x,y) >=dis[u]-dis[lca(u,x)]-dis[lca(u,y)]+dis[z]。lca均可在O(logn)内求出,得证!
推论如下:
判断树上某点u是否在链(x,y)上可以在O(logn)复杂度内求出
求dis< u,(x,y) >,结果为0则在链(x,y)上,反之不在。
求链(x1,y1)和链(x2,y2)的最段距离可以在O(logn)复杂度内求出
设z1=lca(x1,y1),z2=lca(x2,y2),不妨设z1高度更高。则z2到(x1,y1)距离即为链(x2,y2)到(x1,y2)距离
另一个结论是:
树中一系列点的lca和dfs序最小和最大的点的lca相同
该结论等价于定点u和动点v的lca随v的dfs序增大而不减。这个结论很显然通过v的移动可以看出确实如此。
回到本题。先求树中每点的dfs序。如果添加的点u在已知点集中存在dsf序比它小的最大点x和dfs序比它大的最小点y,那么增加的权值和即为u到达链(x,y)的距离,这个结论可以通过实验x和y的不同相对位置得到;如果所有点的dfs序都比y的dfs序大或小,那么只要求出dfs序的最大和最小值对应顶点x和y,求u到链(x,y)距离即可,这可以由上述结论证明正确性。

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<vector>
#include<math.h>
#include<set>
#include<algorithm>
#define ll int
using namespace std;

const int maxn = 100000+10;
int t,n,q;
struct Edge{
    int to,w,next;
}edge[maxn<<2];
int head[maxn],tot;
void add(int u,int v,int w) { edge[tot].to=v,edge[tot].w=w,edge[tot].next=head[u],head[u]=tot++; }

int father[maxn], fa[maxn][20+3], deep[maxn];

bool vis[maxn];
int dis[maxn],dfst[maxn],dfs_clock,id[maxn];
int tmpans;
set<int> se;
set<int>::iterator iter;

void dfs(int u,int f) {
    deep[u] = deep[f]+1; father[u]=f; dfst[u]=++dfs_clock; id[dfs_clock]=u;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to,w=edge[i].w;;
        if(v==f) continue;
        dis[v]=dis[u]+w;
        dfs(v,u);
    }
}

void init() {
    deep[0]=0,dis[1]=0,dfs_clock=0;
    dfs(1,0);
    for(ll i=1;i<=n;i++) fa[i][0]=father[i];
    for(ll j=1;j<=18;j++) for(ll i=1;i<=n;i++) fa[i][j]=fa[fa[i][j-1]][j-1];
}

int lca(int x,int y) {
    if (deep[x] < deep[y]) swap(x, y);
    int delta = deep[x] - deep[y];
    for(int j=0;j<=18;j++) if ((1<<j) & delta) x = fa[x][j];
    if (x == y) return x;
    for(int j=18;j>=0;j--) if (fa[x][j] != fa[y][j]) x = fa[x][j], y = fa[y][j];
    return fa[x][0];
}

int fun(int u){
    if(!se.size()) return 0;
    iter=se.lower_bound(dfst[u]);
    int x,y; bool flag=false;
    if(iter!=se.end()){
        y=*iter; y=id[y];
        if(iter!=se.begin()){
            iter--; x=*iter; x=id[x];
            flag=true;
        }
    }
    if(!flag){
        iter=se.begin(); x=*iter; x=id[x];
        iter=se.end(); iter--; y=*iter; y=id[y];
    }
    int tmp=dis[u]-dis[lca(u,x)]-dis[lca(u,y)]+dis[lca(x,y)];
    return tmp;
}
int main(){
    //freopen("a.txt","r",stdin);
    scanf("%d",&t);
    for(int cas=1;cas<=t;cas++){
        scanf("%d%d",&n,&q);
        memset(head,-1,sizeof(head)); tot=0;
        memset(vis,0,sizeof(vis));
        se.clear();
        for(int i=1;i<n;i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); add(u,v,w); add(v,u,w); }
        init(); tmpans=0;
        printf("Case #%d:\n",cas);
        for(int i=1;i<=q;i++){
            int x,u; scanf("%d%d",&x,&u);
            if(x==1&&!vis[u]) { vis[u]=1; tmpans+=fun(u); se.insert(dfst[u]); }
            else if(x==2&&vis[u]) { vis[u]=0; se.erase(dfst[u]); tmpans-=fun(u); }
            printf("%d\n",tmpans);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值