[树链剖分]Query On Tree IV

题意理解

给出一颗含有n个结点(全为白点)的含有边权(可为负)的树,询问q次,有两种操作类型:
C x :将结点x反色(白->黑 or 黑->白)
A :询问图中路径边权和最大的两个白点的路径边权和(此后用“距离”代替)

本题难点

注意不要把线段树和原树和在一起理解
C x 操作使问题动态,需要维护,增加复杂度。
A操作没有固定询问关于某一个结点的距离,可能要维护整张图的最大距离才可以一次出解。

题目思路

树剖+压缩重链到点上+递归更新
特点:重链压缩

  1. Dfs*2(套树剖的版。。。)
  2. Build:要对每一条重链都建线段树,由于刚开始全为白点,所以还要将初始答案求出来。
  3. Modify:动态维护线段树。

Dfs :更新树的属性
fa:父亲的点编号,fav:父亲到当前节点的距离
tid:dfn的值,rnk:tid的反函数(tid[x]=y,rnk[y]=x)
top:重链的顶端的编号,son:重儿子的编号,presum:重链从top到当前点的前缀和。
注意不能把编号、dfn和线段树id弄混了

void Dfs1(int x,int fax) {
    fa[x]=fax;
    sz[x]=1;
    son[x]=-1;
    for(int i=0;i<G[x].size();i++) {
        int v=G[x][i];
        if(v!=fax) {
            Dfs1(v,x);
            sz[x]+=sz[v];
            if(son[x]==-1||sz[son[x]]<sz[v])
                son[x]=v;
        }
        else fav[x]=W[x][i];
    }
}
void Dfs2(int x,int tp) {
    top[x]=tp;
    tid[x]=++dcnt;
    sz[tp]++;
    if(x!=tp)
        presum[tid[x]]=presum[tid[fa[x]]]+fav[x];//前缀和
    rnk[dcnt]=x;
    if(son[x]==-1) return ;
    Dfs2(son[x],tp);
    for(int i=0;i<G[x].size();i++) {
        int v=G[x][i];
        if(v!=son[x]&&v!=fa[x])
            Dfs2(v,v);
    }
}

建树(如图):对每一个重链建一个线段树
重链生成线段树的示意图

重链压缩(如图):将重链压缩到上方重链的节点上
这里写图片描述

更新节点的值(如图):使用multiset存储multiset与set的区别
这里写图片描述

void Build(int l,int r,int id,bool flag) {//当flag为1表示当前为top结点
    if(l==r) {//叶节点
        int u=rnk[l];
        for(int i=0;i<G[u].size();i++) {
            int v=G[u][i];
            if(v!=fa[u]&&top[u]!=top[v]) {
                root[v]=++cnt;//用root记录top对应的线段树的id
                Build(tid[v],tid[v]+sz[v]-1,root[v],1);//对轻边子树建线段树
                ch[u].insert(tree[root[v]].maxl+W[u][i]);//更新u点的值
            }
        }
        Updata(u,id);//对u单点更新,详见下一段代码
        if(flag==1)//注意这里只更新top结点,其他结点不更
            ansx.insert(tree[id].v);
    }
    else {//非叶节点
        int mid=(l+r)>>1;
        tree[id].pl=++cnt;
        tree[id].pr=++cnt;//pl,pr类型为id
        Build(l,mid,tree[id].pl,0);
        Build(mid+1,r,tree[id].pr,0);//id线段树类型分两段处理
        Merge(id,tree[id].pl,tree[id].pr,l,r);//PushUp,利用儿子更新
        if(flag==1)
            ansx.insert(tree[id].v);
    }
}

Build的更新方式:Merge(非叶节点通过儿子更新),Updata(叶节点直接更新)
这里写图片描述
儿子更新:

void Merge(int rt,int lch,int rch,int l,int r) {
    int mid=(l+r)>>1;//Query为区间边权和
    tree[rt].maxl=max(tree[lch].maxl,Query(l,mid+1)+tree[rch].maxl);
    tree[rt].maxr=max(tree[rch].maxr,Query(mid,r)+tree[lch].maxr);
    tree[rt].v=max(tree[lch].maxr+Query(mid,mid+1)+tree[rch].maxl,max(tree[lch].v,tree[rch].v));
}

叶节点更新:
利用叶节点只有一个点的特点直接用点的set更新

void Updata(int u,int id) {
    int d1=-INF,d2=-INF;
    if(ch[u].size()!=0)
        d1=*(ch[u].rbegin());
    if(ch[u].size()>1)
        d2=*(++ch[u].rbegin());
    if(col[u]==0) {
        tree[id].maxl=tree[id].maxr=max(d1,0);
        tree[id].v=max(0,max(d1,d1+d2));
    }
    else {
        tree[id].maxl=tree[id].maxr=d1;
        tree[id].v=max(-INF,d1+d2);     
    }
}
int Query(int l,int r) {//询问[l,r]区间内的边权和,利用性质:任何一个线段树内的dfn是连续的
    if(l>r)
        swap(l,r);
    return presum[r]-presum[l];//dfn连续,可以用前缀和求得
}

动态更新(重链之间Dfs递归更新,重链之内线段更新):
先找到重链之间的道路:Find_path
path存路上的点,pathv存重链之间的距离,找到u到根节点的距离。在Modify之前使用。

void Find_path(int u) {
    while(u!=0) {
        path.push_back(u);
        pathv.push_back(fav[top[u]]);
        u=fa[top[u]];
    }
}

用Modify更新,寻找的时候用i记录
更新时一定要将原答案删除再更新

void Modify(int l,int r,int id,int i,bool flag) {
    if(l==r) {//叶节点
        int u=rnk[l];
        if(i!=0) {//路程没有走完
            int nxt=top[path[i-1]];
            ch[u].erase(ch[u].find(tree[root[nxt]].maxl+pathv[i-1]));//删除已经不存在的答案
            Modify(tid[nxt],tid[nxt]+sz[nxt]-1,root[nxt],i-1,1);//更新
            ch[u].insert(tree[root[nxt]].maxl+pathv[i-1]);//更新完后将答案存入
        }
        if(flag)
            ansx.erase(ansx.find(tree[id].v));//删除旧答案
        Updata(u,id);
        if(flag)
            ansx.insert(tree[id].v);//更新新答案
    }
    else {
        int mid=(l+r)>>1;
        if(tid[path[i]]<=mid)
            Modify(l,mid,tree[id].pl,i,0);
        else Modify(mid+1,r,tree[id].pr,i,0);
        if(flag)
            ansx.erase(ansx.find(tree[id].v));
        Merge(id,tree[id].pl,tree[id].pr,l,r);
        if(flag)
            ansx.insert(tree[id].v);//原理类似于Build
    }
}
完整代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<set>
#define MAXN 100010
#define INF (int)1e9+7
using namespace std;
int n,q,dcnt,cnt;
int tid[MAXN],top[MAXN],rnk[MAXN],sz[MAXN];
int fa[MAXN],son[MAXN],fav[MAXN],presum[MAXN*4];
int col[MAXN],root[MAXN];
multiset<int> ch[MAXN],ansx;
vector<int>G[MAXN],W[MAXN];
vector<int>path,pathv;
struct node{
    int pl,pr,v,maxl,maxr;
}tree[MAXN*4];
int read(){ 
    int f=1,x=0;char s=getchar();    
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}   
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} 
    return x*f; 
}
void Dfs1(int x,int fax) {
    fa[x]=fax;
    sz[x]=1;
    son[x]=-1;
    for(int i=0;i<G[x].size();i++) {
        int v=G[x][i];
        if(v!=fax) {
            Dfs1(v,x);
            sz[x]+=sz[v];
            if(son[x]==-1||sz[son[x]]<sz[v])
                son[x]=v;
        }
        else fav[x]=W[x][i];
    }
}
void Dfs2(int x,int tp) {
    top[x]=tp;
    tid[x]=++dcnt;
    sz[tp]++;
    if(x!=tp)
        presum[tid[x]]=presum[tid[fa[x]]]+fav[x];//前缀和
    rnk[dcnt]=x;
    if(son[x]==-1) return ;
    Dfs2(son[x],tp);
    for(int i=0;i<G[x].size();i++) {
        int v=G[x][i];
        if(v!=son[x]&&v!=fa[x])
            Dfs2(v,v);
    }
}
int Query(int l,int r) {
    if(l>r)
        swap(l,r);
    return presum[r]-presum[l];
}
void Merge(int rt,int lch,int rch,int l,int r) {
    int mid=(l+r)>>1;
    tree[rt].maxl=max(tree[lch].maxl,Query(l,mid+1)+tree[rch].maxl);
    tree[rt].maxr=max(tree[rch].maxr,Query(mid,r)+tree[lch].maxr);
    tree[rt].v=max(tree[lch].maxr+Query(mid,mid+1)+tree[rch].maxl,max(tree[lch].v,tree[rch].v));
}
void Updata(int u,int id) {
    int d1=-INF,d2=-INF;
    if(ch[u].size()!=0)
        d1=*(ch[u].rbegin());
    if(ch[u].size()>1)
        d2=*(++ch[u].rbegin());
    if(col[u]==0) {
        tree[id].maxl=tree[id].maxr=max(d1,0);
        tree[id].v=max(0,max(d1,d1+d2));
    }
    else {
        tree[id].maxl=tree[id].maxr=d1;
        tree[id].v=max(-INF,d1+d2);     
    }
}
void Build(int l,int r,int id,bool flag) {
    if(l==r) {
        int u=rnk[l];
        for(int i=0;i<G[u].size();i++) {
            int v=G[u][i];
            if(v!=fa[u]&&top[u]!=top[v]) {
                root[v]=++cnt;
                Build(tid[v],tid[v]+sz[v]-1,root[v],1);
                ch[u].insert(tree[root[v]].maxl+W[u][i]);
            }
        }
        Updata(u,id);
        if(flag==1)
            ansx.insert(tree[id].v);
    }
    else {
        int mid=(l+r)>>1;
        tree[id].pl=++cnt;
        tree[id].pr=++cnt;
        Build(l,mid,tree[id].pl,0);
        Build(mid+1,r,tree[id].pr,0);
        Merge(id,tree[id].pl,tree[id].pr,l,r);
        if(flag==1)
            ansx.insert(tree[id].v);
    }
}
void Find_path(int u) {
    while(u!=0) {
        path.push_back(u);
        pathv.push_back(fav[top[u]]);
        u=fa[top[u]];
    }
}
void Modify(int l,int r,int id,int i,bool flag) {
    if(l==r) {
        int u=rnk[l];
        if(i!=0) {//路程没有走完
            int nxt=top[path[i-1]];
            ch[u].erase(ch[u].find(tree[root[nxt]].maxl+pathv[i-1]));
            Modify(tid[nxt],tid[nxt]+sz[nxt]-1,root[nxt],i-1,1);
            ch[u].insert(tree[root[nxt]].maxl+pathv[i-1]);
        }
        if(flag)
            ansx.erase(ansx.find(tree[id].v));
        Updata(u,id);
        if(flag)
            ansx.insert(tree[id].v);
    }
    else {
        int mid=(l+r)>>1;
        if(tid[path[i]]<=mid)
            Modify(l,mid,tree[id].pl,i,0);
        else Modify(mid+1,r,tree[id].pr,i,0);
        if(flag)
            ansx.erase(ansx.find(tree[id].v));
        Merge(id,tree[id].pl,tree[id].pr,l,r);
        if(flag)
            ansx.insert(tree[id].v);
    }
}
int main() {
    int u,v,w,sum=0;
    char tag[5];
    n=read();
    for(int i=1;i<n;i++) {
        u=read(),v=read(),w=read();
        G[u].push_back(v),W[u].push_back(w);//G:儿子节点
        G[v].push_back(u),W[v].push_back(w);//W:(u,v)边权
    }
    Dfs1(1,0);
    memset(sz,0,sizeof(sz));//将后来没有用的子树大小sz改为0,在DFS2中存top为i的重链长度sz[i]
    Dfs2(1,1);
    root[1]=cnt=1;//初始化起点及其编号
    Build(tid[1],tid[1]+sz[1]-1,1,1);//对1号重链建树
    q=read();
    while(q--) {
        scanf("%s",tag);
        if(tag[0]=='C') {
            u=read();
            if(!col[u])
                sum++;
            else sum--;
            col[u]^=1;
            path.clear();
            pathv.clear();
            Find_path(u);//先找路径再修改
            Modify(tid[1],tid[1]+sz[1]-1,root[1],path.size()-1,1);//在修改时使用dfn一般比较方便
        }//          dfn       dfn         id      路程     是否为top
        else {
            if(sum==n)
                printf("They have disappeared.\n");
            else if(sum==n-1)
                printf("0\n");
            else printf("%d\n",*ansx.rbegin());
        }
    }
    return 0;
}
  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值