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));
}
代码的核心在于线段树合并的函数 update 和 merge 。直接在节点上操作,可以省去很多麻烦。
再把问题转移到树上来。考虑关于 \(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]染色(树链剖分+线段树合并)的更多相关文章
- 【BZOJ2243】[SDOI2011]染色 树链剖分+线段树
[BZOJ2243][SDOI2011]染色 Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的 ...
- bzoj2243[SDOI2011]染色 树链剖分+线段树
2243: [SDOI2011]染色 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 9012 Solved: 3375[Submit][Status ...
- B20J_2243_[SDOI2011]染色_树链剖分+线段树
B20J_2243_[SDOI2011]染色_树链剖分+线段树 一下午净调这题了,争取晚上多做几道. 题意: 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成 ...
- 2243: [SDOI2011]染色 树链剖分+线段树染色
给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段), 如“112221”由3段组 ...
- BZOJ2243 [SDOI2011]染色(树链剖分+线段树合并)
题目链接 BZOJ2243 树链剖分 $+$ 线段树 线段树每个节点维护$lc$, $rc$, $s$ $lc$代表该区间的最左端的颜色,$rc$代表该区间的最右端的颜色 $s$代表该区间的所有连续颜 ...
- BZOJ2243 (树链剖分+线段树)
Problem 染色(BZOJ2243) 题目大意 给定一颗树,每个节点上有一种颜色. 要求支持两种操作: 操作1:将a->b上所有点染成一种颜色. 操作2:询问a->b上的颜色段数量. ...
- 【bzoj1959】[Ahoi2005]LANE 航线规划 树链剖分+线段树
题目描述 对Samuel星球的探险已经取得了非常巨大的成就,于是科学家们将目光投向了Samuel星球所在的星系——一个巨大的由千百万星球构成的Samuel星系. 星际空间站的Samuel II巨型计算 ...
- 洛谷P3313 [SDOI2014]旅行 题解 树链剖分+线段树动态开点
题目链接:https://www.luogu.org/problem/P3313 这道题目就是树链剖分+线段树动态开点. 然后做这道题目之前我们先来看一道不考虑树链剖分之后完全相同的线段树动态开点的题 ...
- 【BZOJ-2325】道馆之战 树链剖分 + 线段树
2325: [ZJOI2011]道馆之战 Time Limit: 40 Sec Memory Limit: 256 MBSubmit: 1153 Solved: 421[Submit][Statu ...
- POJ3237 (树链剖分+线段树)
Problem Tree (POJ3237) 题目大意 给定一颗树,有边权. 要求支持三种操作: 操作一:更改某条边的权值. 操作二:将某条路径上的边权取反. 操作三:询问某条路径上的最大权值. 解题 ...
随机推荐
- 连接 MySQL 报错:Lost connection to MySQL server at 'reading authorization packet', system error: 34
报错信息: Lost connection to MySQL server at 解决方案: use mysql; ; flush privileges; 参考: https://blog.csdn. ...
- 2、docker镜像管理
Docker镜像管理 镜像是Docker容器的基础,想运行一个Docker容器就需要有镜像.我们上面已经学会了使用search搜索镜像.那么这个镜像是怎么创建的呢? 创建镜像 镜像的创建有以下几种方法 ...
- I-country
I-country 在\(n\times m\)的网格图中,给出每个格子的权值,寻找有k个格子的凸联通块,使包含的权值最大,\(N,M≤15,K≤225\). 解 我们首先要知道凸联通块的定义 从任意 ...
- 廖雪峰Java14Java操作XML和JSON-1XML-2DOM
XML是一种数据表示形式. 可以描述非常复杂的数据数据结构 用于传输和传输数据 DOM:Document Object Model DOM模型就是把XML文档作为一个树形结构,从根结点开始,每个节点都 ...
- 0901NOIP模拟测试赛后总结
突然想学迪哥列一下分数线搞清楚自己和别人的差距. rank1- 5- 6-分. 差距很大啊.尤其是和某kyh.大家都开玩笑说天皇是个变态.但是事实摆在这儿,同样坐在机房这么长的时间,人家又AK了. 我 ...
- zabbix被监控端代理设置
zabbix被监控端代理设置 安装zabbix-agent客户端 rpm -ivh https://repo.zabbix.com/zabbix/4.0/rhel/7/x86_64/zabbix-re ...
- javascript 数组的方法(一)
栈方法(后进先出) ArrayObj.push():就是向数组末尾添加新的元素,返回的是数组新的长度. ArrayObj.pop():就是向数组中删除数组最后一个元素并且返回该元素.如果数组为空就返回 ...
- opencv-访问Mat中每个像素的值
参考:[OpenCV]访问Mat中每个像素的值(新) 膜拜大佬 以下例子代码均针对8位单通道灰度图. 1 .ptr和[]操作符 Mat最直接的访问方法是通过.ptr<>函数得到一行的指 ...
- 在熟练使用2B铅笔前,请不要打开Axure
在互联网产品领域,Axure已成为产品经理.产品设计师以及交互设计师的必备工具,从某种程度讲,Axure帮助我们建立低保真模型,便于与用户的需求验证,也帮助我们构思交互细节,使前端和开发人员更容易理解 ...
- Less主要用法
一.混合(Mixin) 原css中的样式如: .header { width:200px; height:100px; } .header .word{ color:red; } less中的写法可以 ...