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;
}
需求响应动态冰蓄冷系统与需求响应策略的优化研究(Matlab代码实现)内容概要:本文围绕“需求响应动态冰蓄冷系统与需求响应策略的优化研究”展开,基于Matlab代码实现,重点探讨了冰蓄冷系统在电力需求响应背景下的动态建模与优化调度策略。研究结合实际电力负荷与电价信号,构建系统能耗模型,利用优化算法对冰蓄冷系统的运行策略进行求解,旨在降低用电成本、平衡电网负荷,并提升能源利用效率。文中还提及该研究为博士论文复现,涉及系统建模、优化算法应用与仿真验证等关键技术环节,配套提供了完整的Matlab代码资源。; 适合人群:具备一定电力系统、能源管理或优化算法基础,从事科研或工程应用的研究生、高校教师及企业研发人员,尤其适合开展需求响应、综合能源系统优化等相关课题研究的人员。; 使用场景及目标:①复现博士论文中的冰蓄冷系统需求响应优化模型;②学习Matlab在能源系统建模与优化中的具体实现方法;③掌握需求响应策略的设计思路与仿真验证流程,服务于科研项目、论文写作或实际工程方案设计。; 阅读建议:建议结合提供的Matlab代码逐模块分析,重点关注系统建模逻辑与优化算法的实现细节,按文档目录顺序系统学习,并尝试调整参数进行仿真对比,以深入理解不同需求响应策略的效果差异。
综合能源系统零碳优化调度研究(Matlab代码实现)内容概要:本文围绕“综合能源系统零碳优化调度研究”,提供了基于Matlab代码实现的完整解决方案,重点探讨了在高比例可再生能源接入背景下,如何通过优化调度实现零碳排放目标。文中涉及多种先进优化算法(如改进遗传算法、粒子群优化、ADMM等)在综合能源系统中的应用,涵盖风光场景生成、储能配置、需求响应、微电网协同调度等多个关键技术环节,并结合具体案例(如压缩空气储能、光热电站、P2G技术等)进行建模与仿真分析,展示了从问题建模、算法设计到结果验证的全流程实现过程。; 适合人群:具备一定电力系统、能源系统或优化理论基础,熟悉Matlab/Simulink编程,从事新能源、智能电网、综合能源系统等相关领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①开展综合能源系统低碳/零碳调度的科研建模与算法开发;②复现高水平期刊(如SCI/EI)论文中的优化模型与仿真结果;③学习如何将智能优化算法(如遗传算法、灰狼优化、ADMM等)应用于实际能源系统调度问题;④掌握Matlab在能源系统仿真与优化中的典型应用方法。; 阅读建议:建议结合文中提供的Matlab代码与网盘资源,边学习理论模型边动手调试程序,重点关注不同优化算法在调度模型中的实现细节与参数设置,同时可扩展应用于自身研究课题中,提升科研效率与模型精度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值