• [kuangbin]树链剖分 D


    https://vjudge.net/contest/251031#problem/D
    https://blog.csdn.net/kirito_acmer/article/details/51201994
    树链剖分加线段树染色维护
    我的难点在于线段树的维护 lc,rc,nc,lazy

    lc rc 主要是用来看线段树分开的区间进行连续询问(就是合并的时候)在分界点处颜色是否一样(我一直wa就是这个方面少考虑了一个地方)

    nc是这个区间上有几个颜色

    lazy标记记录的事这个区间完全被某个颜色覆盖了——加快速度

    先来看看代码

    一系列准备的值:

    #include <iostream>
    #include <cstdio>
    #include <string.h>
    #include <algorithm>
    #include <cmath>
    #define lson rt<<1,left,mid
    #define rson rt<<1|1,mid+1,right
    #define ls rt<<1
    #define rs rt<<1|1
    #define mid ((left + right) >> 1)
    using namespace std;
    typedef long long ll;
    const int maxn = 1e5 + 1e3;
    int n,m,p;
    int V[maxn];
    int LC,RC;//LCA向上跳的时候维护
    //邻接表
    struct node{
        int to,pre;
    }e[maxn << 1];
    int id[maxn],cnt;
    //线段树
    int lc[maxn << 2],rc[maxn << 2],lazy[maxn << 2],nc[maxn << 2];
    //dfs1
    int siz[maxn],dep[maxn],fa[maxn],son[maxn];
    //dfs2
    int top[maxn],num_id[maxn],id_num[maxn];
    int tot;
    

     这里面有个LC,RC用到的时候我再说

    然后初始化加边操作

    void init()
    {
        memset(id,-1,sizeof(id));
        memset(son,0,sizeof(son));
        cnt = tot = 0;
    }
    void add(int from,int to)
    {
        e[cnt].to = to;
        e[cnt].pre = id[from];
        id[from] = cnt++;
    }
    void dfs1(int now,int f,int depth)
    {
        siz[now] = 1;
        fa[now] = f;
        dep[now] = depth;
    
        for(int i = id[now];~i;i = e[i].pre)
        {
            int to = e[i].to;
            if(to != f)
            {
                dfs1(to,now,depth+1);
                siz[now] += siz[to];
                if(siz[to] > siz[son[now]])
                    son[now] = to;
            }
        }
    }
    void dfs2(int now,int rt)
    {
        top[now] = rt;
        num_id[now] = ++tot;
        id_num[tot] = now;
    
        if(!son[now]) return;
    
        dfs2(son[now],rt);
    
        for(int i = id[now];~i;i = e[i].pre)
        {
            int to = e[i].to;
            if(to != son[now] && to != fa[now])
            {
                dfs2(to,to);
            }
        }
    }
    

     关于向上更新的操作针对于lc,rc,还有nc,很好理解,这里要注意内部边界颜色颜色相同的时候

    void pup(int rt)
    {
        lc[rt] = lc[ls];
        rc[rt] = rc[rs];
        nc[rt] = nc[ls] + nc[rs];
        if(rc[ls] == lc[rs])
            --nc[rt];
    }
    

     基础建树操作

    void build(int rt,int left,int right)
    {
        lazy[rt] = 0;
        nc[rt] = 0;
        if(left == right)
        {
    
            //cout<<left<<" : "<<" "<<id_num[left] <<" "<<V[id_num[left]]<<endl;
            lc[rt] = rc[rt] = V[id_num[left]];
            nc[rt] = 1;
            return;
        }
        build(lson);
        build(rson);
        pup(rt);
    }
    

     向下更新操作,针对lazy标记的意义没有啥难度

    void pdown(int rt,int left,int right)
    {
        if(lazy[rt])
        {
            int lt = lazy[rt];
            lc[ls] = rc[ls] = lc[rs] = rc[rs] = lc[rt];
            nc[ls] = nc[rs] = 1;
            lazy[rs] = lt;
            lazy[ls] = lt;
            lazy[rt] = 0;
        }
    }
    

     重头戏之LCA+线段树更新操作

    不在一条链上常规更新,在一条链上常规更新,主意好区间范围updata_lca没什么难度

    对于updata函数,找到最大包围区间更新lc,rc,nc,lazy很容易,向下的更新,递归的区间操作,向上的维护,都还是基础的线段树的考察

    void updata(int rt,int left,int right,int l,int r,int k)
    {
        if(l <= left && right <= r)
        {
            //cout<<left<<" "<<right<<endl;
            lc[rt] = rc[rt] = k;
            nc[rt] = 1;
            lazy[rt] = 1;
            return;
        }
    
        pdown(rt,left,right);
    
        if(l <= mid)
            updata(lson,l,r,k);
        if(r > mid)
            updata(rson,l,r,k);
    
        pup(rt);
    }
    void updata_lca(int x,int y,int k)
    {
        while(top[x] != top[y])
        {
            if(dep[top[x]] < dep[top[y]])swap(x,y);
            updata(1,1,tot,num_id[top[x]],num_id[x],k);
            x = fa[top[x]];
        }
        if(dep[x] < dep[y])swap(x,y);
        updata(1,1,tot,num_id[y],num_id[x],k);
    }
    

     重头戏之LCA + 线段树查询操作

    先看query_lca函数这时候我们要用到LC,RC,prex,prey这四个变量了,因为LCA借助top数组进行链链跳,把大区间分开了,所以我们在合并大区间的时候

    很容易理解prex,prey分别记录的是上一条链的左头颜色和右头颜色

    LC,RC呢就是Query后记录的当前区间左头又

    当没有到一条链的时候我们能保证topx是深度深的,所以对于连接的时候只需要判断prex ?= Rc 即可

    但是到了一条链了,你就得想这个点是从哪里并入这条链的,两种情况顶端和非顶端非顶端的情况不会交换判断prex 和 RC

    顶端的时候交换了就要判断prey ?= LC的关系了,但是没什么prex和prey肯定有一个是-1没啥

    这个地方我倒是想明白了,但是还解释不出来,还是动手画一画这两种情况模拟一下就好了

    int query(int rt,int left,int right,int l,int r)
    {
        int res = 0;
        if(left == l)LC = lc[rt];
        if(right == r)RC = rc[rt];
        if(l <= left && right <= r)
        {
            return nc[rt];
        }
    
        pdown(rt,left,right);
    
        if(l <= mid)res += query(lson,l,r);
        if(r > mid)res += query(rson,l,r);
        if(l <= mid && r > mid)
        {
            if(rc[ls] == lc[rs])res--;
        }
        return res;
    }
    int querty_lca(int x,int y)
    {
        int res = 0;
        int prex = -1,prey = -1;
        while(top[x] != top[y])
        {
            if(dep[top[x]] < dep[top[y]]){
                    swap(x,y);
                    swap(prex,prey);
            }
            res += query(1,1,tot,num_id[top[x]],num_id[x]);
            if(prex == RC)res--;
            prex = LC;
            x = fa[top[x]];
        }
        if(dep[x] < dep[y])
        {
            swap(x,y);
            swap(prex,prey);
        }
        res += query(1,1,tot,num_id[y],num_id[x]);
        if(prex == RC) res--;
        if(prey == LC) res--;
        return res;
    }
    

     好了这个题就结束了

    int main()
    {
        while(~scanf("%d%d",&n,&m))
        {
            init();
            for(int i=1;i<=n;++i)
            {
                scanf("%d",&V[i]);
            }
            int from,to;
            for(int i=1;i<=n-1;i++)
            {
                scanf("%d%d",&from,&to);
                add(from,to);
                add(to,from);
            }
            dfs1(1,0,1);
            dfs2(1,1);
            build(1,1,tot);
            //cout<<lc[8]<<" "<<lc[9]<<" "<<lc[5]<<" "<<lc[12]<<" "<<lc[13]<<" "<<lc[7]<<endl;
            char op[10];
            int x,y,z;
            for(int i=1;i<=m;++i)
            {
                scanf("%s",op);
                if(op[0]=='Q'){
                    scanf("%d%d",&x,&y);
                    printf("%d
    ",querty_lca(x,y));
                }
                else
                {
                    scanf("%d%d%d",&x,&y,&z);
                    updata_lca(x,y,z);
                }
            }
        }
        return 0;
    }
    
  • 相关阅读:
    由于版本依赖造成的YUM段错误
    CodeDom系列事件(event)定义和反射调用
    CodeSmith模板引擎系列二文件目录树
    F#初试打印目录文件树
    在IIS上SSL的部署和启动SSL安全
    CodeDom系列二程序基本结构符号三角形问题
    CodeDom系列目录
    CodeDom系列四Code生成
    CodeDom六实体类生成示例
    CodeDom系列五动态编译
  • 原文地址:https://www.cnblogs.com/DF-yimeng/p/9595427.html
Copyright © 2020-2023  润新知