HDU 5296 Annoying Problem 树链剖分 LCA 倍增法

本文详细介绍了HDU5296 Annoying Problem的解题思路,包括如何通过树链剖分、线段树、倍增等数据结构和算法解决该问题。主要内容涉及节点集合S的维护、LCA(最近公共祖先)的查找、关键节点的确定及边权重的更新,最终输出使集合S中所有点联通的最小子树的边权和。

HDU 5296 Annoying Problem


题目链接:hdu 5296

题意:在一棵给定的具有边权的树,一个节点的集合S(初始为空),给定Q个操作,每个操作增加或删除S中的一个点,每个操作之后输出使集合S中所有点联通的最小子树的边权和。

思路:最小子树上的节点的充要条件:

  • 节点为(S集合中所有点的LCA)的子节点;
  • 节点有一个子孙为S集合中的点。

那么我们给每个节点都开一个标记数组,初始为零,每加入一个节点,就把从这个节点到根节点路径上的点的值都+1,反之-1,这样通过对每个单节点值的查询就能知道它是不是最小子树上的节点。
然后对于每一次节点的增加和删除都有两种情况:
第一种:加入或删除新节点不会改变最小子树的根节点,那么就必然存在一个节点是这个新节点的祖先,那么我们找到这个深度最深的祖先在最小子树中的祖先就是关键节点,可以倍增求;
第二种:加入或删除新节点会改变最小子树的根节点,那么这个关键节点就是旧子树的根节点。
求得关键节点之后直接加上或减去两点之间的边的权值和就OK。

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <queue>
#include <set>
#include <algorithm>
#define Lson o<<1,l,mid
#define Rson o<<1|1,mid+1,r
using namespace std;
const int maxn=120010+5;//最大节点数
const int maxm =maxn*2;//最大边条数
const int DEG =20;

int v[maxm];//v[i]表示第i条边指向的节点
int Prev[maxm];//Prev[i]表示i的兄弟边
int ed[maxm][3];//存储的是边的信息
int info[maxn];//表示最后插入的与节点i相连的边
int Q[maxn];//用于实现队列和栈
int idx[maxn];//存储的是节点i在所属链的编号 从叶子到根依次增加
int dep[maxn];//节点的深度
int size[maxn];//链的轻重
int belong[maxn];//表示节点i所属边的编号
int father[maxn];//节点i的直接父亲
bool vis[maxn];//剖分回溯时使用的标记
int head[maxn];//第i条链的头部节点
int len[maxn];//第i条链的长度
int l,r,ans,cnt=0;//cnt为链的条数
int N,nedge=0;//nedge为边的条数 N为总点数
int pos[maxn];//树链剖分后各店的位置
int val[maxn];//求得节点到根节点所经过边的权值和
int rk[maxn];//节点的DFS序
int frk[maxn];//DFS序的反函数
int fa[maxn][DEG];//节点i的2^j倍祖先
/******************树链剖分部分****************************/
//树链剖分部分
inline void insert(int x,int y,int c){
    ++nedge;
    v[nedge]=y;Prev[nedge]=info[x];info[x]=nedge;
    ed[nedge][0]=x;ed[nedge][1]=y;ed[nedge][2]=c;
}
void split(){
    memset(dep,-1,sizeof(dep));
    l=0;
    dep[Q[r=1]=1]=0;
    father[1]=-1;
    while(l<r){
        int x=Q[++l];
        vis[x]=false;
        for(int y=info[x];y!=-1;y=Prev[y]){
            if(dep[v[y]]==-1){
                dep[Q[++r]=v[y]]=dep[x]+1;
                father[v[y]]=x;
            }
        }

    }

    for(int i=N;i;i--){
        int x=Q[i],p=-1;
        size[x]=1;
        for(int y=info[x];y!=-1;y=Prev[y]){
            if(vis[v[y]]){
                size[x]+=size[v[y]];
                if(p==-1||size[v[y]]>size[p])
                    p=v[y];
            }
        }
        if(p==-1){
            idx[x]=len[++cnt]=1;
            belong[head[cnt]=x]=cnt;
        }
        else {
            idx[x]=++len[belong[x]=belong[p]];
            head[belong[x]]=x;
        }
        vis[x]=true;
    }
}
void get_pos(){
    int Pos[maxn];
    Pos[1]=0;
    for(int i=2;i<=cnt;i++)Pos[i]=Pos[i-1]+len[i-1];
    for(int i=1;i<=N;i++)pos[i]=Pos[belong[i]]+idx[i];
}

/******************线段树部分****************************/
//区间修改单点查询  也可以使用树状数组
int addv[maxn*4];
void push_down(int o,int l,int r){
    if(l==r) return ;
    addv[o<<1]+=addv[o];
    addv[o<<1|1]+=addv[o];
    addv[o]=0;
}

void update(int o,int l,int r,int L,int R,int val){
    if(L<=l&&R>=r){
        addv[o]+=val;
    }
    else {
        push_down(o,l,r);
        int mid=(l+r)>>1;
        if(L<=mid)update(Lson,L,R,val);
        if(R>mid)update(Rson,L,R,val);
    }
}
int quarry(int o,int l,int r,int pos){
    if(l==r) return addv[o];
    push_down(o,l,r);
    int mid=(l+r)>>1;
    if(pos>mid)return quarry(Rson,pos);
    else return quarry(Lson,pos);
}



/******************剖分后对树的边修改部分********************/

void change(int a,int b,int val){
    int fu=head[belong[a]],fv=head[belong[b]];
    while(fu!=fv){
        update(1,1,N,pos[b],pos[fv],val);
        b=father[fv];
        fv=head[belong[b]];
    }
    update(1,1,N,pos[b],pos[a],val);

}


/***************BFS求得倍增关系和到根节点边的权值和************/

void bfs(int root){
    for(int i=1;i<=nedge;i++){
        if(dep[ed[i][0]]<dep[ed[i][1]])continue;
        val[ed[i][0]]=ed[i][2];
    }
    fa[root][0]=root;
    dep[root]=0;
    l=r=0;
    val[root]=0;
    Q[r++]=root;
    while(l<r){
        int u=Q[l++];
        val[u]=val[u]+val[fa[u][0]];
        for(int i=1;i<DEG;i++){
            fa[u][i]=fa[fa[u][i-1]][i-1];
        }
        for(int i=info[u];i!=-1;i=Prev[i]){
            int drc=v[i];
            if(drc==father[u])continue;
            dep[drc]=dep[u]+1;
            fa[drc][0]=u;
            Q[r++]=drc;
        }
    }
}


/*********************求DFS序及其反函数**********************/
void fdg(int root){

    int tt=0;
    Q[tt++]=root;
    rk[root]=0;
    int rr=0;
    while(tt>0){
        int u=Q[--tt];
        rk[u]=rr++;
        for(int i=info[u];i!=-1;i=Prev[i]){
            int drc=v[i];
            if(drc==fa[u][0])continue;
            Q[tt++]=drc;
        }
    }
    for(int i=1;i<=N;i++){
        frk[rk[i]]=i;
    }
}
/*******************倍增法求LCA******************************/
int lca(int a,int b){
    if(dep[a]>dep[b])swap(a,b);
    int hu=dep[a],hv=dep[b];
    int tu=a,tv=b;
    for(int i=0,tt=hv-hu;tt;i++,tt/=2){
        if(tt&1) tv=fa[tv][i];
    }
    if(tu==tv) return tu;
    for(int i=DEG-1;~i;i--){
        if(fa[tu][i]==fa[tv][i])continue;
        tu=fa[tu][i];
        tv=fa[tv][i];
    }
    return fa[tu][0];
}
/********************求当前集合的LCA***********************/
set<int> Set;
set<int>::iterator it;
int get_lca(){
    if(Set.empty())
        return -1;
    if(Set.size()==1){
        return frk[*Set.begin()];
    }
    else {
        it=Set.end();
        it--;
        return lca(frk[*Set.begin()],frk[*it]);
    }
}

/*******************倍增法求关键节点*************************/

int get_key_node(int u){
    if(quarry(1,1,N,pos[u])) return u;
    for(int i=DEG-1;~i;i--){
        int tp=quarry(1,1,N,pos[fa[u][i]]);
        if(tp)continue;
        u=fa[u][i];
    }
    return fa[u][0];
}

/****初始化****/
void init(){
    memset(addv,0,sizeof(addv));
    nedge=0;
    Set.clear();
    cnt=0;
    memset(info,-1,sizeof(info));
}
/**********分情况处理询问***************/
void solve(int a,int b){
    int lca1,lca2;
    int key_node;
    if(a==1){
        if(Set.find(rk[b])==Set.end()){
            lca1=get_lca();
            Set.insert(rk[b]);
            lca2=get_lca();
            if(lca1==-1){
                change(1,b,1);
            }
            else {
                if(lca2==lca1){
                    key_node=get_key_node(b);
                    ans+=(val[b]-val[key_node]);
                }
                else {
                    ans+=(val[b]+val[lca1]-2*val[lca2]);
                }
                change(1,b,1);
            }
        }
    }
    else {
        if(Set.find(rk[b])!=Set.end()){
            lca1=get_lca();
            Set.erase(rk[b]);
            lca2=get_lca();
            change(1,b,-1);
            if(lca2==-1){
            }
            else {
                if(lca1==lca2){
                    key_node=get_key_node(b);
                    ans-=(val[b]-val[key_node]);
                }
                else {
                    ans-=(val[b]+val[lca2]-2*val[lca1]);
                }
            }
        }
    }
}
int main(){
//    freopen("100.in","r",stdin);
//    freopen("data.out","w",stdout);
    int T,M,cas=0;
    scanf("%d",&T);
    while(T--){
        init();
        printf("Case #%d:\n",++cas);
        scanf("%d%d",&N,&M);
        int a,b,c;
        for(int i=1;i<N;i++){
            scanf("%d%d%d",&a,&b,&c);
            insert(a,b,c);
            insert(b,a,c);
        }
        split();
        get_pos();
        bfs(1);
        fdg(1);
        ans=0;
        while(M--){
            scanf("%d%d",&a,&b);
            solve(a,b);
            printf("%d\n",ans);
        }
    }
    return 0;
}

另外刚刚游泳的时候想到了一个方法不用倍增。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <queue>
#include <set>
#include <algorithm>
#define Lson o<<1,l,mid
#define Rson o<<1|1,mid+1,r
using namespace std;
const int maxn=120010+5;//最大节点数
const int maxm =maxn*2;//最大边条数
const int DEG =20;
int v[maxm];//v[i]表示第i条边指向的节点
int Prev[maxm];//Prev[i]表示i的兄弟边
int ed[maxm][3];//存储的是边的信息
int info[maxn];//表示最后插入的与节点i相连的边
int Q[maxn];//用于实现队列和栈
int idx[maxn];//存储的是节点i在所属链的编号 从叶子到根依次增加
int dep[maxn];//节点的深度
int size[maxn];//链的轻重
int belong[maxn];//表示节点i所属边的编号
int father[maxn];//节点i的直接父亲
bool vis[maxn];//剖分回溯时使用的标记
int head[maxn];//第i条链的头部节点
int len[maxn];//第i条链的长度
int l,r,ans,cnt=0;//cnt为链的条数
int N,nedge=0;//nedge为边的条数 N为总点数
int pos[maxn];//树链剖分后各店的位置
int val[maxn];//求得节点到根节点所经过边的权值和
int rk[maxn];//节点的DFS序
int frk[maxn];//DFS序的反函数
/******************树链剖分部分****************************/
//树链剖分部分
inline void insert(int x,int y,int c){
    ++nedge;
    v[nedge]=y;Prev[nedge]=info[x];info[x]=nedge;
    ed[nedge][0]=x;ed[nedge][1]=y;ed[nedge][2]=c;
}
void split(){
    memset(dep,-1,sizeof(dep));
    l=0;
    dep[Q[r=1]=1]=0;
    father[1]=-1;
    while(l<r){
        int x=Q[++l];
        vis[x]=false;
        for(int y=info[x];y!=-1;y=Prev[y]){
            if(dep[v[y]]==-1){
                dep[Q[++r]=v[y]]=dep[x]+1;
                father[v[y]]=x;
            }
        }

    }

    for(int i=N;i;i--){
        int x=Q[i],p=-1;
        size[x]=1;
        for(int y=info[x];y!=-1;y=Prev[y]){
            if(vis[v[y]]){
                size[x]+=size[v[y]];
                if(p==-1||size[v[y]]>size[p])
                    p=v[y];
            }
        }
        if(p==-1){
            idx[x]=len[++cnt]=1;
            belong[head[cnt]=x]=cnt;
        }
        else {
            idx[x]=++len[belong[x]=belong[p]];
            head[belong[x]]=x;
        }
        vis[x]=true;
    }
}
void get_pos(){
    int Pos[maxn];
    Pos[1]=0;
    for(int i=2;i<=cnt;i++)Pos[i]=Pos[i-1]+len[i-1];
    for(int i=1;i<=N;i++)pos[i]=Pos[belong[i]]+idx[i];
}
/******************线段树部分****************************/
//区间修改单点查询  也可以使用树状数组
int addv[maxn*4];
void push_down(int o,int l,int r){
    if(l==r) return ;
    addv[o<<1]+=addv[o];
    addv[o<<1|1]+=addv[o];
    addv[o]=0;
}
void update(int o,int l,int r,int L,int R,int val){
    if(L<=l&&R>=r){
        addv[o]+=val;
    }
    else {
        push_down(o,l,r);
        int mid=(l+r)>>1;
        if(L<=mid)update(Lson,L,R,val);
        if(R>mid)update(Rson,L,R,val);
    }
}
int quarry(int o,int l,int r,int pos){
    if(l==r) return addv[o];
    push_down(o,l,r);
    int mid=(l+r)>>1;
    if(pos>mid)return quarry(Rson,pos);
    else return quarry(Lson,pos);
}
/******************剖分后对树的边修改部分****************************/

void change(int a,int b,int val){
    int fu=head[belong[a]],fv=head[belong[b]];
    while(fu!=fv){
        update(1,1,N,pos[b],pos[fv],val);
        b=father[fv];
        fv=head[belong[b]];
    }
    update(1,1,N,pos[b],pos[a],val);

}
/*********************求DFS序及其反函数**************************/
void fdg(int root){
    for(int i=1;i<=nedge;i++){
        if(dep[ed[i][0]]<dep[ed[i][1]])continue;
        val[ed[i][0]]=ed[i][2];
    }
    val[root]=0;
    int tt=0;
    Q[tt++]=root;
    rk[root]=0;
    int rr=0;
    while(tt>0){
        int u=Q[--tt];
        rk[u]=rr++;
        val[u]=val[u]+val[father[u]];
        for(int i=info[u];i!=-1;i=Prev[i]){
            int drc=v[i];
            if(drc==father[u])continue;
            Q[tt++]=drc;
        }
    }
    for(int i=1;i<=N;i++){
        frk[rk[i]]=i;
    }
}
/*******************求LCA******************************/
int lca(int a,int b){
    int fu=head[belong[a]],fv=head[belong[b]];
    if(dep[fu]>dep[fv]){
        swap(fu,fv);
    }
    while(fu!=fv){
        b=father[fv];
        fv=head[belong[b]];
        if(dep[fu]>dep[fv]){
            swap(fu,fv);
            swap(a,b);
        }
    }
    if(dep[a]>dep[b])swap(a,b);
    return a;
}
/**********************求当前集合的LCA***********************/
set<int> Set;
set<int>::iterator it;
int get_lca(){
    if(Set.empty())
        return -1;
    if(Set.size()==1){
        return frk[*Set.begin()];
    }
    else {
        it=Set.end();
        it--;
        return lca(frk[*Set.begin()],frk[*it]);
    }
}

/*******************求关键节点*************************/

int get_key_node(int u){
    int fu=head[belong[u]];
    while(!quarry(1,1,N,pos[fu])){
        u=father[fu];
        fu=head[belong[u]];
    }
    while(!quarry(1,1,N,pos[u])){
        u=father[u];
    }
    return u;
}

/****初始化****/
void init(){
    memset(addv,0,sizeof(addv));
    nedge=0;
    Set.clear();
    cnt=0;
    memset(info,-1,sizeof(info));
}
/**********分情况处理询问***************/
void solve(int a,int b){
    int lca1,lca2;
    int key_node;
    if(a==1){
        if(Set.find(rk[b])==Set.end()){
            lca1=get_lca();
            Set.insert(rk[b]);
            lca2=get_lca();
            if(lca1==-1){
                change(1,b,1);
            }
            else {
                if(lca2==lca1){
                    key_node=get_key_node(b);
                    ans+=(val[b]-val[key_node]);
                }
                else {
                    ans+=(val[b]+val[lca1]-2*val[lca2]);
                }
                change(1,b,1);
            }
        }
    }
    else {
        if(Set.find(rk[b])!=Set.end()){
            lca1=get_lca();
            Set.erase(rk[b]);
            lca2=get_lca();
            change(1,b,-1);
            if(lca2==-1){
            }
            else {
                if(lca1==lca2){
                    key_node=get_key_node(b);
                    ans-=(val[b]-val[key_node]);
                }
                else {
                    ans-=(val[b]+val[lca2]-2*val[lca1]);
                }
            }
        }
    }
}
int main(){
//    freopen("100.in","r",stdin);
//    freopen("data.out","w",stdout);
    int T,M,cas=0;
    scanf("%d",&T);
    while(T--){
        init();
        printf("Case #%d:\n",++cas);
        scanf("%d%d",&N,&M);
        int a,b,c;
        for(int i=1;i<N;i++){
            scanf("%d%d%d",&a,&b,&c);
            insert(a,b,c);
            insert(b,a,c);
        }
        split();
        get_pos();
        fdg(1);
        ans=0;
        while(M--){
            scanf("%d%d",&a,&b);
            solve(a,b);
            printf("%d\n",ans);
        }
    }
    return 0;
}
根据原作 https://pan.quark.cn/s/459657bcfd45 的源码改编 Classic-ML-Methods-Algo 引言 建立这个项目,是为了梳理和总结传统机器学习(Machine Learning)方法(methods)或者算法(algo),和各位同仁相互学习交流. 现在的深度学习本质上来自于传统的神经网络模型,很大程度上是传统机器学习的延续,同时也在不少时候需要结合传统方法来实现. 任何机器学习方法基本的流程结构都是通用的;使用的评价方法也基本通用;使用的一些数学知识也是通用的. 本文在梳理传统机器学习方法算法的同时也会顺便补充这些流程,数学上的知识以供参考. 机器学习 机器学习是人工智能(Artificial Intelligence)的一个分支,也是实现人工智能最重要的手段.区别于传统的基于规则(rule-based)的算法,机器学习可以从数据中获取知识,从而实现规定的任务[Ian Goodfellow and Yoshua Bengio and Aaron Courville的Deep Learning].这些知识可以分为四种: 总结(summarization) 预测(prediction) 估计(estimation) 假想验证(hypothesis testing) 机器学习主要关心的是预测[Varian在Big Data : New Tricks for Econometrics],预测的可以是连续性的输出变量,分类,聚类或者物品之间的有趣关联. 机器学习分类 根据数据配置(setting,是否有标签,可以是连续的也可以是离散的)和任务目标,我们可以将机器学习方法分为四种: 无监督(unsupervised) 训练数据没有给定...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值