zoukankan      html  css  js  c++  java
  • 【BZOJ2243】[SDOI2011] 染色(树链剖分)

    点此看题面

    大致题意: 有一棵(n)个节点的无根树和(m)个操作,且每个节点有一个颜色。操作有两种:一种是将两点树上路径之间所有点染成颜色(c),另一种是询问两点树上路径之间颜色段的数量。

    树链剖分

    这道题的核心算法应该是树链剖分

    一个简单的小问题

    先让我们来思考一个简单的小问题。

    对于两个区间,我们该如何求出这两个区间合并后有多少个颜色段?

    事实上,我们只需知道这两个区间中每个区 颜色段的数量(Sum)最左端的颜色(Left)最右端的颜色(Right),就可以得到合并后新区间的信息了。

    我们只需要比较 左区间的(Right) 和右区间的(Left),若两个颜色相同,则合并后的新区间颜色段数量为 左区间的(Sum+)右区间的(Sum-1)(如果出现这样的情况,左区间与右区间合并后的中间部分会连接成一个颜色段,因此要减(1)),否则,合并后的新区间颜色段数量就为 左区间的(Sum+)右区间的(Sum)

    而合并后区间的(Left)即为 左区间的(Left)(Right)即为 右区间的(Right)(这应该还是比较好理解的吧)。

    这样一来,我们就解决如何更新区间信息的问题了。

    用树剖解决此题

    这样一来,就不难想到怎么用树链剖分解决此题了。

    我们可以用一棵线段树来维护树剖后的序列的信息。

    然后就用 树剖求两点树上路径信息 的做法来询问即可。

    一个需要注意的地方就是,在树剖求两点树上路径信息的过程中,信息的合并也要采用上面的方法。

    其实这还是一道挺裸的题。

    代码

    #include<bits/stdc++.h>
    #define max(x,y) ((x)>(y)?(x):(y))
    #define min(x,y) ((x)<(y)?(x):(y))
    #define abs(x) ((x)<0?-(x):(x))
    #define LL long long
    #define ull unsigned long long
    #define swap(x,y) (x^=y,y^=x,x^=y)
    #define N 100000
    #define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
    using namespace std;
    int n,ee=0,a[N+5],lnk[N+5];
    struct edge
    {
        int to,nxt;
    }e[2*N+5];
    class FIO
    {
        private:
            #define Fsize 100000
            #define tc() (FinNow==FinEnd&&(FinEnd=(FinNow=Fin)+fread(Fin,1,Fsize,stdin),FinNow==FinEnd)?EOF:*FinNow++)
            #define pc(ch) (FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,FoutSize,stdout),Fout[(FoutSize=0)++]=ch))
            int f,FoutSize,OutputTop;char ch,Fin[Fsize],*FinNow,*FinEnd,Fout[Fsize],OutputStack[Fsize];double w;
        public:
            FIO() {FinNow=FinEnd=Fin;}
            inline void read(int &x) {x=0,f=1;while(!isdigit(ch=tc())) f=ch^'-'?1:-1;while(x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));x*=f;}
            inline void read_char(char &x) {while(isspace(x=tc()));}
            inline void read_string(string &x) {x="";while(isspace(ch=tc()));while(x+=ch,!isspace(ch=tc()));}
            inline void write(int x) {if(!x) return (void)pc('0');if(x<0) pc('-'),x=-x;while(x) OutputStack[++OutputTop]=x%10+48,x/=10;while(OutputTop) pc(OutputStack[OutputTop]),--OutputTop;}
            inline void write_char(char x) {pc(x);}
            inline void write_string(string x) {register int i,len=x.length();for(i=0;i<len;++i) pc(x[i]);}
            inline void end() {fwrite(Fout,1,FoutSize,stdout);}
    }F;
    class TreeChainDissection//树链剖分
    {
        private:
            #define PushUp(x) (node[x]=node[x<<1]+node[x<<1|1])
            #define PushDown(x) (~flag[x]?(node[x<<1]=node[x<<1|1]=key(1,flag[x<<1]=flag[x],flag[x<<1|1]=flag[x]),flag[x]=-1):0)
            int d,fa[N+5],son[N+5],sz[N+5],Top[N+5],dfn[N+5],fac[N+5],Depth[N+5],flag[N<<2];
            struct key//存储区间信息 
            {
                int Sum,l,r;
                key(int x=0,int y=-1,int z=-1):Sum(x),l(y),r(z){}
                inline bool empty() {return !(Sum|(~l)|(~r));}//判断区间是否为空
            }node[N<<2];
            inline friend key operator + (key x,key y)//区间信息合并
            {
                if(x.empty()) return y;//如果x为空,返回y
                if(y.empty()) return x;//如果y为空,返回x
                return key(x.Sum+y.Sum-!(x.r^y.l),x.l,y.r);//否则,将两个区间信息合并
            }
            inline void dfs1(int x)
            {
                register int i;
                for(sz[x]=1,i=lnk[x];i;i=e[i].nxt)
                {
                    if(!(fa[x]^e[i].to)) continue;
                    fa[e[i].to]=x,Depth[e[i].to]=Depth[x]+1,dfs1(e[i].to),sz[x]+=sz[e[i].to];
                    if(sz[e[i].to]>sz[son[x]]) son[x]=e[i].to; 
                }
            }
            inline void dfs2(int x,int col)
            {
                register int i;
                if(son[fac[dfn[x]=++d]=x]) dfs2(son[x],col);
                for(Top[x]=col,i=lnk[x];i;i=e[i].nxt)
                {
                    if(!(fa[x]^e[i].to&&son[x]^e[i].to)) continue;
                    dfs2(e[i].to,e[i].to);
                }
            }
            inline void Build(int l,int r,int rt)//建树 
            {
                flag[rt]=-1;
                if(!(l^r)) return (void)(node[rt]=key(1,a[fac[l]],a[fac[l]]));
                register int mid=l+r>>1;
                Build(l,mid,rt<<1),Build(mid+1,r,rt<<1|1),PushUp(rt);
            }
            inline void Update(int l,int r,int rt,int ul,int ur,int x)//修改
            {
                if(ul<=l&&r<=ur) return (void)(node[rt]=key(1,flag[rt]=x,x));
                register int mid=l+r>>1;
                PushDown(rt);//下推标记
                if(ul<=mid) Update(l,mid,rt<<1,ul,ur,x);
                if(ur>mid) Update(mid+1,r,rt<<1|1,ul,ur,x);
                PushUp(rt);//上传子节点信息
            }
            inline key Query(int l,int r,int rt,int ql,int qr)//询问操作
            {
                if(ql<=l&&r<=qr) return node[rt];
                register int mid=l+r>>1;register key res;
                PushDown(rt);//下推标记
                if(ql<=mid) res=Query(l,mid,rt<<1,ql,qr);
                if(qr>mid) res=res+Query(mid+1,r,rt<<1|1,ql,qr);
                PushUp(rt);
                return res;//返回区间信息
            }
        public:
            inline void Init(int rt) {dfs1(fa[rt]=rt),dfs2(rt,1),Build(1,n,1);}
            inline void Update_Path(int l,int r,int x)//修改两点树上路径信息
            {
                while(Top[l]^Top[r])
                {
                    if(Depth[Top[l]]<Depth[Top[r]]) swap(l,r);
                    Update(1,n,1,dfn[Top[l]],dfn[l],x),l=fa[Top[l]];
                }
                dfn[l]<dfn[r]?Update(1,n,1,dfn[l],dfn[r],x):Update(1,n,1,dfn[r],dfn[l],x);
            }
            inline int Query_Path(int l,int r)//询问两点树上路径信息
            {
                register int res=0,t1=-1,t2=-1;register key t;
                while(Top[l]^Top[r])
                {
                    if(Depth[Top[l]]<Depth[Top[r]]) swap(l,r),swap(t1,t2);
                    t=Query(1,n,1,dfn[Top[l]],dfn[l]),res+=t.Sum-!(t.r^t1),t1=t.l,l=fa[Top[l]];//注意采用区间信息合并的方式来更新信息
                }
                return (res+(dfn[l]<dfn[r]?((t=Query(1,n,1,dfn[l],dfn[r])).Sum-!(t1^t.l)-!(t2^t.r)):((t=Query(1,n,1,dfn[r],dfn[l])).Sum-!(t2^t.l)-!(t1^t.r))));
            }
    }S; 
    int main()
    {
        register int i,Q,x,y,z;register char op;
        for(F.read(n),F.read(Q),i=1;i<=n;++i) F.read(a[i]);
        for(i=1;i<n;++i) F.read(x),F.read(y),add(x,y),add(y,x);
        for(S.Init(1);Q;--Q)
        {
            F.read_char(op),F.read(x),F.read(y);
            if(op^'Q') F.read(z),S.Update_Path(x,y,z);
            else F.write(S.Query_Path(x,y)),F.write_char('
    ');
        }
        return F.end(),0;
    }
    
  • 相关阅读:
    Appium Android 元素定位方法 原生+H5
    Eclipse下Python的MySQLdb的安装以及相关问题
    Python模块包中__init__.py文件的作用
    如何调用另一个python文件中的代码
    Python单元测试框架unittest使用方法讲解
    python利用unittest进行测试用例执行的几种方式
    python随机生成手机号码
    eclipse 安装python后pydev不出现
    Appium 如何模拟返回按键
    python分布式进程
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ2243.html
Copyright © 2011-2022 走看看