Luogu P2486 [SDOI2011]染色

题面

题目描述

输入输出格式

输入格式:

输出格式:

对于每个询问操作,输出一行答案。

输入输出样例

输入样例:

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

说明:

思路

好久没打树剖了,今天和水星爹爹 \(solo\) 这道题的时候输掉了(你爹爹还是你爹爹) \(qwq\) 。

进入正题。早在成都的时候 \(czk\) 巨佬就一直推荐我写这道题。

这题对细节的考虑非常多。 --czk

不过我一直懒得开(其实是自己太蒻了),后来开题也都是写挂了。其实这题并不难,很板,主要就是考虑如何用线段树/树链剖分维护区间的颜色以及颜色段数。

对于一个线段树节点,维护以下内容:

  • \(l,r:\) 该节点维护的区间范围 \([l,r]\) ;
  • \(maxl,maxr:\) 该节点所维护的区间的最左边节点颜色和最右边节点颜色;
  • \(data:\) 该节点所维护区间的颜色段数;
  • \(tag:\) 即懒惰标记,初始化时 \(tag=0\) ;

我们不妨先解决这样一个问题:

给定一个序列(颜色串),每次修改一个区间 \([l,r]\) 使之颜色(权值)全部变为一个数,或询问一个区间 \([l,r]\) 中的颜色段数是多少。

那么有了上面约定的节点储存信息,这样的询问是可合并的,很方便用线段树解决。

具体来说,我们的线段树操作这样做:

struct SegmentTree
{
int l,r,maxl,maxr,data,tag;
#define l(a) tree[a].l
#define r(a) tree[a].r
#define ml(a) tree[a].maxl
#define mr(a) tree[a].maxr
#define d(a) tree[a].data
#define t(a) tree[a].tag
void init(){l=r=maxl=maxr=data=tag=0;}
}tree[MAXN<<2];//zcysky的线段树写法qwq
inline SegmentTree merge(SegmentTree x,SegmentTree y)//重点(敲黑板):线段树合并。将左边的x和右边的y合并起来
{
if(!x.data) return y;
if(!y.data) return x;
SegmentTree re;
re.init();
re.maxl=x.maxl,re.maxr=y.maxr,re.data=x.data+y.data;
if(x.maxr==y.maxl) re.data--;
return re;
}
inline void update(int p)//上传更新
{
ml(p)=ml(p<<1),mr(p)=mr(p<<1|1);
d(p)=d(p<<1)+d(p<<1|1);
if(mr(p<<1)==ml(p<<1|1)) d(p)--;//该区间中间,即左区间的最右端和右区间的最左端相等时,颜色段数--
}
inline void pushdown(int p)//标记下传
{
if(t(p))
{
ml(p<<1)=mr(p<<1)=t(p<<1)=t(p);
ml(p<<1|1)=mr(p<<1|1)=t(p<<1|1)=t(p);
d(p<<1)=d(p<<1|1)=1;
t(p)=0;
}
}
void build(int p,int ll,int rr)//建树
{
l(p)=ll,r(p)=rr;
if(ll==rr)
{
ml(p)=mr(p)=b[ll];
d(p)=1;
return ;
}
int mid=(l(p)+r(p))>>1;
build(p<<1,ll,mid);
build(p<<1|1,mid+1,rr);
update(p);
}
void change(int p,int ll,int rr,int k)//修改操作:将[ll,rr]区间的权值修改为k
{
if(ll<=l(p)&&r(p)<=rr)
{
d(p)=1,ml(p)=mr(p)=t(p)=k;
return ;
}
pushdown(p);
int mid=(l(p)+r(p))>>1;
if(mid>=ll) change(p<<1,ll,rr,k);
if(mid<rr) change(p<<1|1,ll,rr,k);
update(p);
}
SegmentTree ask(int p,int ll,int rr)//询问操作,直接返回一个线段树,方便合并
{
if(ll<=l(p)&&r(p)<=rr) return tree[p];
pushdown(p);
int mid=(l(p)+r(p))>>1;
if(mid>=rr) return ask(p<<1,ll,rr);
else if(mid<ll) return ask(p<<1|1,ll,rr);
else return merge(ask(p<<1,ll,rr),ask(p<<1|1,ll,rr));
}

代码的核心在于线段树合并的函数 updatemerge 。直接在节点上操作,可以省去很多麻烦。

再把问题转移到树上来。考虑关于 \(u,v\) 两节点之间颜色段数的询问,其实就相当于将 \(u\) 往上跳到 \(LCA(u,v)\) 所合并出的线段树与 \(v\) 往上跳到 \(LCA(u,v)\) 所合并出的线段树进行合并。在 \(u,v\) 分别向上跳时,每次合并时,按照树链剖分的节点命名规律,先处理的是线段树中的右边部分,再处理左边部分,所以要将新处理的部分与已经处理过的部分进行合并。当 \(u,v\) 都跳到了同一条链上时,因为 \(LCA(u,v)\) 处储存的是需要合并的两个线段树节点的最左端颜色,所以就不能再用之前的 merge() 函数,而要特判从 \(u\) 开始合并出的线段树 与从 \(v\) 开始合并出的线段树的最左端点 maxl 是否相同。代码如下:

int x=read(),y=read();//x,y即上述的u,v
SegmentTree xx,yy;//xx为从x开始合并出的线段树,yy亦然
xx.init(),yy.init();//一开始要清空!
while(st[x]!=st[y])
{
if(dep[st[x]]<dep[st[y]]) swap(x,y),swap(xx,yy);//交换x,y时也要记得交换xx和yy
xx=merge(ask(1,id[st[x]],id[x]),xx);
x=fa[st[x]];
}
if(id[x]>id[y]) swap(x,y),swap(xx,yy);
yy=merge(ask(1,id[x],id[y]),yy);
int ans=xx.data+yy.data;
if(xx.maxl==yy.maxl) ans--;//特判
printf("%d\n",ans);

其他操作就和普通的树剖板子很类似了,在这里就不做过多讲解,详情见代码。

AC代码

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
int n,m,a[MAXN],b[MAXN],fa[MAXN],dep[MAXN],sz[MAXN],son[MAXN];
int cnt,top[MAXN],to[MAXN<<1],nex[MAXN<<1];
int tot,id[MAXN],st[MAXN];
struct SegmentTree
{
int l,r,maxl,maxr,data,tag;
#define l(a) tree[a].l
#define r(a) tree[a].r
#define ml(a) tree[a].maxl
#define mr(a) tree[a].maxr
#define d(a) tree[a].data
#define t(a) tree[a].tag
void init(){l=r=maxl=maxr=data=tag=0;}
}tree[MAXN<<2];
inline int read()
{
int re=0;
char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) re=(re<<3)+(re<<1)+ch-'0',ch=getchar();
return re;
}
inline char readc()
{
char ch=getchar();
while(!isalpha(ch)) ch=getchar();
return ch;
}
void dfs1(int now)
{
for(int i=top[now];i;i=nex[i])
{
if(to[i]==fa[now]) continue;
fa[to[i]]=now,sz[to[i]]=1,dep[to[i]]=dep[now]+1;
dfs1(to[i]);
sz[now]+=sz[to[i]];
if(sz[to[i]]>sz[son[now]]) son[now]=to[i];
}
}
void dfs2(int now,int line_top)
{
id[now]=++tot,b[tot]=a[now],st[now]=line_top;
if(!son[now]) return ;
dfs2(son[now],line_top);
for(int i=top[now];i;i=nex[i])
{
if(to[i]==fa[now]||to[i]==son[now]) continue;
dfs2(to[i],to[i]);
}
}
inline SegmentTree merge(SegmentTree x,SegmentTree y)
{
if(!x.data) return y;
if(!y.data) return x;
SegmentTree re;
re.init();
re.maxl=x.maxl,re.maxr=y.maxr,re.data=x.data+y.data;
if(x.maxr==y.maxl) re.data--;
return re;
}
inline void update(int p)
{
ml(p)=ml(p<<1),mr(p)=mr(p<<1|1);
d(p)=d(p<<1)+d(p<<1|1);
if(mr(p<<1)==ml(p<<1|1)) d(p)--;
}
inline void pushdown(int p)
{
if(t(p))
{
ml(p<<1)=mr(p<<1)=t(p<<1)=t(p);
ml(p<<1|1)=mr(p<<1|1)=t(p<<1|1)=t(p);
d(p<<1)=d(p<<1|1)=1;
t(p)=0;
}
}
void build(int p,int ll,int rr)
{
l(p)=ll,r(p)=rr;
if(ll==rr)
{
ml(p)=mr(p)=b[ll];
d(p)=1;
return ;
}
int mid=(l(p)+r(p))>>1;
build(p<<1,ll,mid);
build(p<<1|1,mid+1,rr);
update(p);
}
void change(int p,int ll,int rr,int k)
{
if(ll<=l(p)&&r(p)<=rr)
{
d(p)=1,ml(p)=mr(p)=t(p)=k;
return ;
}
pushdown(p);
int mid=(l(p)+r(p))>>1;
if(mid>=ll) change(p<<1,ll,rr,k);
if(mid<rr) change(p<<1|1,ll,rr,k);
update(p);
}
SegmentTree ask(int p,int ll,int rr)
{
if(ll<=l(p)&&r(p)<=rr) return tree[p];
pushdown(p);
int mid=(l(p)+r(p))>>1;
if(mid>=rr) return ask(p<<1,ll,rr);
else if(mid<ll) return ask(p<<1|1,ll,rr);
else return merge(ask(p<<1,ll,rr),ask(p<<1|1,ll,rr));
}
int main()
{
n=read(),m=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=0;i<n-1;i++)
{
int x=read(),y=read();
to[++cnt]=y,nex[cnt]=top[x],top[x]=cnt;
to[++cnt]=x,nex[cnt]=top[y],top[y]=cnt;
}
fa[1]=sz[1]=dep[1]=1;
dfs1(1);
dfs2(1,1);
build(1,1,n);
while(m--)
{
char opt=readc();
if(opt=='C')
{
int x=read(),y=read(),z=read();
while(st[x]!=st[y])
{
if(dep[st[x]]<dep[st[y]]) swap(x,y);
change(1,id[st[x]],id[x],z);
x=fa[st[x]];
}
if(id[x]>id[y]) swap(x,y);
change(1,id[x],id[y],z);
}
else if(opt=='Q')
{
int x=read(),y=read();
SegmentTree xx,yy;
xx.init(),yy.init();
while(st[x]!=st[y])
{
if(dep[st[x]]<dep[st[y]]) swap(x,y),swap(xx,yy);
xx=merge(ask(1,id[st[x]],id[x]),xx);
x=fa[st[x]];
}
if(id[x]>id[y]) swap(x,y),swap(xx,yy);
yy=merge(ask(1,id[x],id[y]),yy);
int ans=xx.data+yy.data;
if(xx.maxl==yy.maxl) ans--;
printf("%d\n",ans);
}
}
return 0;
}

Luogu P2486 [SDOI2011]染色(树链剖分+线段树合并)的更多相关文章

  1. 【BZOJ2243】[SDOI2011]染色 树链剖分+线段树

    [BZOJ2243][SDOI2011]染色 Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的 ...

  2. bzoj2243[SDOI2011]染色 树链剖分+线段树

    2243: [SDOI2011]染色 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 9012  Solved: 3375[Submit][Status ...

  3. B20J_2243_[SDOI2011]染色_树链剖分+线段树

    B20J_2243_[SDOI2011]染色_树链剖分+线段树 一下午净调这题了,争取晚上多做几道. 题意: 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成 ...

  4. 2243: [SDOI2011]染色 树链剖分+线段树染色

    给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段), 如“112221”由3段组 ...

  5. BZOJ2243 [SDOI2011]染色(树链剖分+线段树合并)

    题目链接 BZOJ2243 树链剖分 $+$ 线段树 线段树每个节点维护$lc$, $rc$, $s$ $lc$代表该区间的最左端的颜色,$rc$代表该区间的最右端的颜色 $s$代表该区间的所有连续颜 ...

  6. BZOJ2243 (树链剖分+线段树)

    Problem 染色(BZOJ2243) 题目大意 给定一颗树,每个节点上有一种颜色. 要求支持两种操作: 操作1:将a->b上所有点染成一种颜色. 操作2:询问a->b上的颜色段数量. ...

  7. 【bzoj1959】[Ahoi2005]LANE 航线规划 树链剖分+线段树

    题目描述 对Samuel星球的探险已经取得了非常巨大的成就,于是科学家们将目光投向了Samuel星球所在的星系——一个巨大的由千百万星球构成的Samuel星系. 星际空间站的Samuel II巨型计算 ...

  8. 洛谷P3313 [SDOI2014]旅行 题解 树链剖分+线段树动态开点

    题目链接:https://www.luogu.org/problem/P3313 这道题目就是树链剖分+线段树动态开点. 然后做这道题目之前我们先来看一道不考虑树链剖分之后完全相同的线段树动态开点的题 ...

  9. 【BZOJ-2325】道馆之战 树链剖分 + 线段树

    2325: [ZJOI2011]道馆之战 Time Limit: 40 Sec  Memory Limit: 256 MBSubmit: 1153  Solved: 421[Submit][Statu ...

  10. POJ3237 (树链剖分+线段树)

    Problem Tree (POJ3237) 题目大意 给定一颗树,有边权. 要求支持三种操作: 操作一:更改某条边的权值. 操作二:将某条路径上的边权取反. 操作三:询问某条路径上的最大权值. 解题 ...

随机推荐

  1. CAS机制详解

    目录 1. 定义 2. 实现原理 3. 无版本号CAS实战说明 4. CAS机制在Java中的应用 5. CAS的缺点 1. CPU开销过大 2. 不能保证代码块的原子性 3. ABA问题 6. JA ...

  2. JS流程控制语句 来来回回(Do...while循环) 先执行后判断 do while结构的基本原理和while结构是基本相同的,但是它保证循环体至少被执行一次。

    来来回回(Do...while循环) do while结构的基本原理和while结构是基本相同的,但是它保证循环体至少被执行一次.因为它是先执行代码,后判断条件,如果条件为真,继续循环. do...w ...

  3. Atcoder arc096

    C:Half and Half 几个if语句贪心算一算就好了 #include<cstdio> #include<algorithm> using namespace std; ...

  4. JavaSE_11_File类、递归

    1.1 概述File类 java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建.查找和删除等操作. 1.2 构造方法 public File(String pathname ...

  5. 为什么Java中的String是设计成不可变的?(Why String is immutable in java)

    There are many reasons due to the string class has been made immutable in Java. These reasons in vie ...

  6. Office宏的基本利用

    前言 Office宏,译自英文单词Macro.宏是Office自带的一种高级脚本特性,通过VBA代码,可以在Office中去完成某项特定的任务,而不必再重复相同的动作,目的是让用户文档中的一些任务自动 ...

  7. WCF服务编程-基础

    WCF是微软建立新一代的分布式应用及面向服务应用的标准平台,是基于原有.NET Framework 2.0的扩展.虽然在WCF发布不久就已经在项目中使用WCF技术了.但是由于在项目中还没有较大规模的应 ...

  8. 03. 将pdb调试文件包含到.vsix包中

    vs插件如何把pdb文件打包进去,方便记录日志和调试 <PropertyGroup> <CopyLocalLockFileAssemblies>true</CopyLoc ...

  9. Linux 日期时间命令

    cal : 显示日历 -1 显示一个月的月历 -3 显示系统前一个月,当前月,下一个月的月历 -s  显示星期天为一个星期的第一天,默认的格式 -m 显示星期一为一个星期的第一天 -j  显示在当年中 ...

  10. mtk 的conferrence call建立流程

    (重点看main_log与) 抓mtk log: 1.*#*#82533284#*#*      进入抓log UI 2.*#*#825364#*#*      进入工程模式 3.进入"Lo ...