bzoj 2243 [SDOI2011]染色 题解(线段树,树链剖分)

最近看我好久没写博客了是不是。。。

其原因是因为我最近期末复习,比较忙,周一到周五平均睡眠时间晚上12:00才能完成刷题任务,更别说写博客了。只有周六周日能抽出点时间写点。。。

从今天开始,我不会再写所有我做过的题了(太多了,写题解的速度肯定没有刷题的速度快)。所以我就选一些比较有意思的,有意义的,扩展思维的,写写博客。(或者是那些我WA了几百遍的。。。

好吧。正式开始

原题链接:
bzoj
洛谷

题意简述

给定一棵带点权树,要支持两种操作:

  1. 路径赋值:从 u u u v v v的路径上的点权都变成一个指定的数 c c c
  2. 路径计算:计算 u u u v v v的路径上的点权有多少"相同块"。如: 112221 112221 112221中有三个相同块: 11 11 11, 222 222 222, 1 1 1

输入

第一行一个正整数 n , m n,m n,m,表示节点数和操作数。 ( n , m &lt; = 1 e 5 ) (n,m&lt;=1e5) (n,m<=1e5)
接下来一行 n n n个数,表示 n n n个节点的初始点权。
接下来 n − 1 n-1 n1行每行一个 u , v u,v u,v,表示 u , v u,v u,v之间有连边。
接下来 m m m行,每行是这样的形式:

  1. Q   a   b Q\ a\ b Q a b a , b a,b a,b进行路径计算操作
  2. C   a   b   c C\ a\ b\ c C a b c a , b a,b a,b进行路径赋值操作,值赋为 c ( 0 &lt; = c &lt; = 1 e 9 ) c(\red{0&lt;=}c&lt;=1e9) c(0<=c<=1e9)

输出

对于每个 Q Q Q打头的操作,输出答案

样例

输入
6 5
2 2 1 2 1 1
1 2
1 3
2 4
2 5
2 6
Q 3 5
C 2 1 1
Q 3 5
C 5 1 2
Q 3 5
输出
3
1
2

思路

首先我们做树剖的题要知道在区间上怎么做,然后才能丢到树上。

part1. 区间上怎么做

这个。。。能做?仔细看看, c &lt; = 1 e 9 \red{c&lt;=1e9} c<=1e9。我艹!

为啥这个东西毒瘤?大概是经过了这样一个心路历程:

  1. s s s表示区间的段数量
  2. 然后 s = s= s=左右儿子的 s s s之和

然后我们很容易举出反例。。。如(其中 ∣ | 是左右儿子的分割线):
1 2 ∣ 2 3 \begin{matrix} 1 &amp; 2 &amp; \red{|} &amp; 2 &amp; 3 \end{matrix} 1223
然后左右儿子相加是 4 4 4,而实际上正确答案应该是 3 3 3
然后就放弃了

B U T BUT BUT,经过一些更多的举反例,我们会发现:错误答案和正确答案要么一样,要么就差 1 1 1。而且肯定是多 1 1 1,不会是少 1 1 1(这个很显然吧)。

啥时候会多 1 1 1呢?就是中间重合的时候。有没有别的情况呢?。。。显然没有

所以我们考虑把 左 右 断 点 的 颜 色 也 维 护 上 \red{左右断点的颜色也维护上} ,然后检查:

左儿子的右端点 是否等于 右儿子的左端点

如果发生了这种情况,就左右相加之后 − 1 -1 1,否则直接左右相加即珂。

然后是询问。我们算得左右半区间的答案后,合并答案的过程也不是以前做的加一下就珂以的,而是要像上面判一下左儿子的右端点是否等于右儿子的左端点,如果等于也要减一。(这也就告诉我们,我们查询答案的函数 Q u e r y Query Query要返回一个结构体)

part2. 放到树上

放到树上的话。。。就是分块以下即可。但是要注意一点:

要按顺序合并!!!

举个栗子:从 u u u v v v的路径上,正确的分块会分出来 a , b , c , d a,b,c,d a,b,c,d四个部分,然后如果合并不当,顺序乱了,就会导致错误。所以,我们要开两个结构体(就是上面 Q u e r y Query Query返回的结构体,当然也是线段树上节点的结构体) x , y x,y x,y,其中 x x x记录 u u u上的路径, y y y记录 v v v上的路径。然后到最后,我们把在同一块里面的路径夹在 x x x y y y之间。然后你会发现,我们从 u u u v v v的路径是由两部分构成的:
u − &gt; L C A u-&gt;LCA u>LCA L C A − &gt; v LCA-&gt;v LCA>v。其中
u − &gt; L C A u-&gt;LCA u>LCA 从 下 往 上 \red{从下往上} 走的,所以最后我们还要把答案反一下合并。怎么反呢?当然不是 O ( n ) O(n) O(n) r e v e r s e reverse reverse,只要把左端点颜色和右端点颜色换一下就珂以了。

代码:

#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define N 100100
    class Graph
    {
        public:
            int head[N];
            int EdgeCount;
            struct Edge
            {
                int To,Label,Next;
            }Ed[N<<1];
 
            void clear()
            {
                memset(Ed,-1,sizeof(Ed));
                memset(head,-1,sizeof(head));
                EdgeCount=0;
            }
            void AddEdge(int u,int v,int w)
            {
                ++EdgeCount;
                Ed[EdgeCount]=(Edge){v,w,head[u]};
                head[u]=EdgeCount;
            }
 
            int Start(int u)
            {
                return head[u];
            }
            int To(int u)
            {
                return Ed[u].To;
            }
            int Label(int u)
            {
                return Ed[u].Label;
            }
            int Next(int u)
            {
                return Ed[u].Next;
            }
    }G;void Add(int u,int v,int w){G.AddEdge(u,v,w);G.AddEdge(v,u,w);}
    int n,q;
    int w[N];
    void Input()
    {
        scanf("%d%d",&n,&q);
        for(int i=1;i<=n;++i) scanf("%d",&w[i]);
        G.clear();
        for(int i=1;i<n;++i)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            Add(u,v,1);
        }
    }
 
    int deep[N],size[N],fa[N],son[N];
    void DFS1(int u,int f)
    {
        deep[u]=(f==-1)?1:deep[f]+1;
        size[u]=1;
        fa[u]=f;
 
        son[u]=-1;int Max=-1;
        for(int i=G.Start(u);~i;i=G.Next(i))
        {
            int v=G.To(i);
            if (v!=f)
            {
                DFS1(v,u);
                size[u]+=size[v];
                if (size[v]>Max)
                {
                    Max=size[v];
                    son[u]=v;
                }
            }
        }
    }
    int DFSid[N];int cnt=0;
    int top[N];
    void DFS2(int u,int topu)
    {
        DFSid[u]=++cnt;
        top[u]=topu;
        if (son[u]==-1) return;
 
        DFS2(son[u],topu);
        for(int i=G.Start(u);~i;i=G.Next(i))
        {
            int v=G.To(i);
            if (v!=fa[u] and v!=son[u] and ~v)
            {
                DFS2(v,v);
            }
        }
    }
    int wt[N];
    class SegmentTree
    {
        public:
            struct node
            {
                int l,r;
                int s,a,cl,cr;//段数,lazytag,左端点颜色,右端点颜色
                void clear()
                {
                    l=r=s=a=cl=cr=-1;
                }
            }tree[N<<2];//有很多用的结构体,即使线段树上的一个节点,也珂以表示答案
            //(准确来讲,线段树上的节点不就是表示答案用的么)
            node add(node ls,node rs)//封装合并答案的函数
            //不知道为啥这里重载运算符会错,只能写函数了
            {
                if (ls.s==-1) return rs;
                if (rs.s==-1) return ls;
                node ans=(node){
                    ls.l,
                    rs.r,
                    ls.s+rs.s,
                    -1,
                    ls.cl,
                    rs.cr
                };//看着继承就好了。注意这里是不用管lazytag的
                if (ls.cr==rs.cl) --ans.s;//如果中间部分重合,就要答案减一
                return ans;
            }
            node rev(node x)
            {
                return (node){
                    x.l,
                    x.r,
                    x.s,
                    x.a,
                    x.cr,
                    x.cl//交换一下cl和cr的位置即珂
                };
            }
            #define ls index<<1
            #define rs index<<1|1
 
            #define L tree[index].l
            #define R tree[index].r
            #define S tree[index].s
            #define A tree[index].a
            #define CL tree[index].cl
            #define CR tree[index].cr
            void Update(int index)
            {
                tree[index]=add(tree[ls],tree[rs]);//合并答案
            }
            void BuildTree(int l,int r,int index)
            {
                L=l,R=r;S=0;A=-1;
                //注意A的初始值是-1,因为颜色可能有0。如果明明赋了值0,却被当成了没有赋值的标记,就会很危险。所以把没被赋值的标记设为-1
                if (l==r)
                {
                    S=1;//注意,这里S是1!!!
                    CL=CR=wt[l];
                    return;
                }
                int mid=(l+r)>>1;
                BuildTree(l,mid,ls);
                BuildTree(mid+1,r,rs);
                Update(index);
            }
            void Change1(int x,int index)
            {
                S=1;//这边也是1!!!
                CL=CR=x;
                A=x;
            }
            void PushDown(int index)
            {
                if (~A)
                {
                    Change1(A,ls);
                    Change1(A,rs);
                    A=-1;
                }
            }
            void RChange(int l,int r,int x,int index)
            {
                if (l>R or L>r) return;
                if (l<=L and R<=r) return Change1(x,index);
                PushDown(index);
                RChange(l,r,x,ls);
                RChange(l,r,x,rs);
                Update(index);
            }
            node Query(int l,int r,int index)
            {
                if (l>R or L>r) return (node){-1,-1,-1,-1,-1,-1};
                if (l<=L and R<=r) return tree[index];
                PushDown(index);
                return add(Query(l,r,ls),Query(l,r,rs));
            }
    }T;
    int PathQuery(int u,int v)
    {
        typedef SegmentTree::node nd;//为了简化代码,用了typedef
        nd x,y;
        x.clear();y.clear();//记录u,v上的答案
 
        int ans=0;
        while(top[u]!=top[v])
        {
            if (deep[top[u]]<deep[top[v]]) swap(u,v),swap(x,y);
            nd tmp=T.Query(DFSid[top[u]],DFSid[u],1);
            x=T.add(tmp,x);
            //小小的提一下,在外面调用函数是不能SegmentTree::add的,必须要有一个SegmentTree类型的东西,然后用.运算符调用
            u=fa[top[u]];
        }
        if (deep[u]<deep[v]) swap(u,v),swap(x,y);
        nd tmp=
        T.add(
            T.add(
                T.rev(x),
                T.rev(
                    T.Query(DFSid[v],DFSid[u],1)
                )
            )
            ,y
        );
        //合并rev(x),rev(T.Query(DFSid[v],DFSid[u],1)),y

        return tmp.s;//注意返回的答案是其中的s值,也就是段数
    }
    void PathAdd(int u,int v,int x)//加值是没什么注意的,因为全部赋值有很强的对称性,怎么换都不会有问题
    {
        while(top[u]!=top[v])
        {
            if (deep[top[u]]<deep[top[v]]) swap(u,v);
            T.RChange(DFSid[top[u]],DFSid[u],x,1);
            u=fa[top[u]];
        }
        if (deep[u]>deep[v]) swap(u,v);
        T.RChange(DFSid[u],DFSid[v],x,1);
    }
    void Soviet()
    {
        DFS1(1,-1);
        DFS2(1,1);
        for(int i=1;i<=n;++i)
        {
            wt[DFSid[i]]=w[i];
        }
        T.BuildTree(1,n,1);//树剖预处理的板子
 
        for(int i=1;i<=q;++i)//处理询问
        {
            char o[5];scanf("%s",&o);
            if (o[0]=='Q')
            {
                int u,v;
                scanf("%d%d",&u,&v);
                printf("%d\n",PathQuery(u,v));
            }
            else if (o[0]=='C')
            {
                int u,v,x;
                scanf("%d%d%d",&u,&v,&x);
                PathAdd(u,v,x);
            }
        }
    }
 
    void IsMyWife()
    {
        if (0)
        {
            freopen("","r",stdin);
            freopen("","w",stdout);
        }
        Input();
        Soviet();
    }
};
int main()
{
    Flandle_Scarlet::IsMyWife();
    return 0;
}

回到总题解界面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值