题解 P2486 【[SDOI2011]染色】
写在前面
对于刚学树剖的同学比如我这种大大大蒟蒻来说,做这题会给你带来很大的提升:不仅可以对树剖有更深刻的理解,还可以更好的理解线段树,所以这是一道好题哦
为了更好懂,我一点一点说说思路吧
思路
首先这题题意不难懂,只有两个操作:区间颜色修改和区间查询颜色数量,我们分开来看:
区间查询颜色个数
这是这题的难点,弄懂了以后可以对线段树有个蛮大的提升吧
我们先把问题简化一下,假设这不是一棵树,只是一条连(已经树剖过了),给定每个元素的颜色,问有几段颜色(就是这题中颜色数量的定义),我们怎么做呢?
首先我们可以知道,为了保障时间复杂度,这题肯定是用线段树求解的,最先想到的是叶子节点的颜色个数为1,因为此时不存在有颜色会重复,按线段树的做法,现在要回溯求更大区间的颜色个数了,我们怎样求解呢?
其实分情况讨论一下就可以知道了:见下
第一种情况:
左区间:1231(颜色个数为4) 右区间:222(个数为1)
合并后:1231222(颜色个数为4+1=5)
这是第一种情况:没有重复
我们再来看第二种:
左区间:1231(颜色个数为4)右区间:121(个数为3)
合并后:1231121(颜色个数为4+3-1)
这就是第二种情况了,左区间的最后一个颜色和右区间的第一个颜色重合,也就重复了,所以总数减一
综上所述:我们用数组lc[ ]和rc[ ]表示区间左右颜色,线段树维护区间颜色总数,就可以解决链情况下的此问题了
int lc[maxn << 2];//这里要开4倍大小,因为是对应线段树节点的
int rc[maxn << 2];
void build(int id,int l,int r){
tree[id].l = l;
tree[id].r = r;
if(l == r){
tree[id].c = col[ori[l]];//赋值:叶子颜色
lc[id] = rc[id] = col[ori[l]];//赋值:区间左颜色和区间右颜色
tree[id].sum = 1;//颜色数为1
return ;
}
int mid = l + r >> 1;
build(lid,l,mid);
build(rid,mid + 1,r);
tree[id].sum = tree[lid].sum + tree[rid].sum;
if(rc[lid] == lc[rid])tree[id].sum -= 1;
lc[id] = lc[lid];
rc[id] = rc[rid];
}
void pushdown(int id){
if(tree[id].lazy != 0 && tree[id].l != tree[id].r){
int c = tree[id].lazy;
tree[lid].lazy = tree[rid].lazy = c;//粉刷
tree[lid].c = tree[rid].c = c;
lc[lid] = rc[lid] = lc[rid] = rc[rid] = c;//更新左右
tree[lid].sum = tree[rid].sum = 1;//粉刷完以后只有一种颜色了
tree[id].lazy = 0;
}
}
int query(int id,int l,int r){
pushdown(id);
if(tree[id].l == l && tree[id].r == r){
return tree[id].sum;
}
int mid = tree[id].l + tree[id].r >> 1;
if(mid < l){
return query(rid,l,r);
}
else if(mid >= r){
return query(lid,l,r);
}
else{
int ret = query(lid,l,mid) + query(rid,mid + 1,r);
if(rc[lid] == lc[rid])ret -= 1;
return ret;
}
}
值得注意的是:不要忘记大区间要继承小区间的左右端点颜色
树上查询颜色个数
我们的操作时基于树形的,所以树剖过后,我们树剖的查询函数要略作修改。
如上图:树剖就是把两点之间剖成了若干条链,我们还是要解决不同的链之间颜色重复问题。上图已经很明朗了:解决top[a]与fa[top[a]]颜色重复问题即可:
我写了个函数Qc来查询单点的颜色,其他学过树剖的应该不会太陌生:
int query(int id,int l,int r){
pushdown(id);
if(tree[id].l == l && tree[id].r == r){
return tree[id].sum;
}
int mid = tree[id].l + tree[id].r >> 1;
if(mid < l){
return query(rid,l,r);
}
else if(mid >= r){
return query(lid,l,r);
}
else{
int ret = query(lid,l,mid) + query(rid,mid + 1,r);
if(rc[lid] == lc[rid])ret -= 1;
return ret;
}
}
int Qc(int id,int l,int r){
pushdown(id);
if(tree[id].l == l && tree[id].r == r){
return tree[id].c;
}
int mid = tree[id].l + tree[id].r >> 1;
if(mid < l)return Qc(rid,l,r);
else return Qc(lid,l,r);
}
int Qsum(int x,int y){
int ans = 0,Cson,Cfa;//儿子的颜色,爸爸的颜色
while(top[x] != top[y]){
if(dep[top[x]] < dep[top[y]])swap(x,y);
ans += query(1,pos[top[x]],pos[x]);//累加答案
Cson = Qc(1,pos[top[x]],pos[top[x]]);
Cfa = Qc(1,pos[fa[top[x]]],pos[fa[top[x]]]);
if(Cson == Cfa)ans -= 1;//重复则答案减一
x = fa[top[x]];
}
if(dep[x] > dep[y])swap(x,y);
ans += query(1,pos[x],pos[y]);
return ans;
}
区间修改
与普通的树剖题修改无大异,注意线段树中的颜色数量更新即区间端点继承即可
void update(int id,int c,int l,int r){
pushdown(id);
if(tree[id].l == l && tree[id].r == r){
tree[id].c = c;
tree[id].lazy = c;
tree[id].sum = 1;
lc[id] = rc[id] = c;
return ;
}
int mid = tree[id].l + tree[id].r >> 1;
if(mid < l){
update(rid,c,l,r);
}
else if(mid >= r){
update(lid,c,l,r);
}
else{
update(lid,c,l,mid);
update(rid,c,mid + 1,r);
}
tree[id].sum = tree[lid].sum + tree[rid].sum;
if(rc[lid] == lc[rid])tree[id].sum -= 1;
lc[id] = lc[lid];
rc[id] = rc[rid];
}
void uprange(int x,int y,int c){
while(top[x] != top[y]){
if(dep[top[x]] < dep[top[y]])swap(x,y);
update(1,c,pos[top[x]],pos[x]);
x = fa[top[x]];
}
if(dep[x] > dep[y])swap(x,y);
update(1,c,pos[x],pos[y]);
}
AC代码
就不多提啦,祝大家天天AC!
(重要注释已经打在上面思路部分了,这里直接给代码)
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
int RD(){
int out = 0,flag = 1;char c = getchar();
while(c < '0' || c >'9'){if(c == '-')flag = -1;c = getchar();}
while(c >= '0' && c <= '9'){out = out * 10 + c - '0';c = getchar();}
return flag * out;
}
const int maxn = 100019;
int num,na,nume,cnt;
int head[maxn];
struct Node{int v,nxt;}E[maxn * 2];
void add(int u,int v){
E[++nume].nxt = head[u];
E[nume].v = v;
head[u] = nume;
}
int size[maxn],wson[maxn],dep[maxn],fa[maxn],top[maxn],pos[maxn],ori[maxn];
int col[maxn];
void dfs1(int id,int F){
size[id] = 1;
for(int i = head[id];i;i = E[i].nxt){
int v = E[i].v;
if(v == F)continue;
dep[v] = dep[id] + 1;
fa[v] = id;
dfs1(v,id);
size[id] += size[v];
if(size[v] > size[wson[id]])wson[id] = v;
}
}
void dfs2(int id,int TP){
top[id] = TP;
pos[id] = ++cnt;
ori[cnt] = id;
if(!wson[id])return ;
dfs2(wson[id],TP);
for(int i = head[id];i;i = E[i].nxt){
int v = E[i].v;
if(v == fa[id] || v == wson[id])continue;
dfs2(v,v);
}
}
int lc[maxn << 2];
int rc[maxn << 2];
#define lid (id << 1)
#define rid (id << 1) | 1
struct sag_tree{
int l,r;
int sum,c;//区间颜色总数,叶子颜色
int lazy;//儿子的颜色
}tree[maxn << 2];
void build(int id,int l,int r){
tree[id].l = l;
tree[id].r = r;
if(l == r){
tree[id].c = col[ori[l]];//赋值:叶子颜色
lc[id] = rc[id] = col[ori[l]];//赋值:区间左颜色和区间右颜色
tree[id].sum = 1;//颜色数为1
return ;
}
int mid = l + r >> 1;
build(lid,l,mid);
build(rid,mid + 1,r);
tree[id].sum = tree[lid].sum + tree[rid].sum;
if(rc[lid] == lc[rid])tree[id].sum -= 1;
lc[id] = lc[lid];
rc[id] = rc[rid];
}
void pushdown(int id){
if(tree[id].lazy != 0 && tree[id].l != tree[id].r){
int c = tree[id].lazy;
tree[lid].lazy = tree[rid].lazy = c;//粉刷
tree[lid].c = tree[rid].c = c;
lc[lid] = rc[lid] = lc[rid] = rc[rid] = c;//更新左右
tree[lid].sum = tree[rid].sum = 1;//粉刷完以后只有一种颜色了
tree[id].lazy = 0;
}
}
void update(int id,int c,int l,int r){
pushdown(id);
if(tree[id].l == l && tree[id].r == r){
tree[id].c = c;
tree[id].lazy = c;
tree[id].sum = 1;
lc[id] = rc[id] = c;
return ;
}
int mid = tree[id].l + tree[id].r >> 1;
if(mid < l){
update(rid,c,l,r);
}
else if(mid >= r){
update(lid,c,l,r);
}
else{
update(lid,c,l,mid);
update(rid,c,mid + 1,r);
}
tree[id].sum = tree[lid].sum + tree[rid].sum;
if(rc[lid] == lc[rid])tree[id].sum -= 1;
lc[id] = lc[lid];
rc[id] = rc[rid];
}
int query(int id,int l,int r){
pushdown(id);
if(tree[id].l == l && tree[id].r == r){
return tree[id].sum;
}
int mid = tree[id].l + tree[id].r >> 1;
if(mid < l){
return query(rid,l,r);
}
else if(mid >= r){
return query(lid,l,r);
}
else{
int ret = query(lid,l,mid) + query(rid,mid + 1,r);
if(rc[lid] == lc[rid])ret -= 1;
return ret;
}
}
int Qc(int id,int l,int r){
pushdown(id);
if(tree[id].l == l && tree[id].r == r){
return tree[id].c;
}
int mid = tree[id].l + tree[id].r >> 1;
if(mid < l)return Qc(rid,l,r);
else return Qc(lid,l,r);
}
void uprange(int x,int y,int c){
while(top[x] != top[y]){
if(dep[top[x]] < dep[top[y]])swap(x,y);
update(1,c,pos[top[x]],pos[x]);
x = fa[top[x]];
}
if(dep[x] > dep[y])swap(x,y);
update(1,c,pos[x],pos[y]);
}
int Qsum(int x,int y){
int ans = 0,Cson,Cfa;
while(top[x] != top[y]){
if(dep[top[x]] < dep[top[y]])swap(x,y);
ans += query(1,pos[top[x]],pos[x]);
Cson = Qc(1,pos[top[x]],pos[top[x]]);
Cfa = Qc(1,pos[fa[top[x]]],pos[fa[top[x]]]);
if(Cson == Cfa)ans -= 1;
x = fa[top[x]];
}
if(dep[x] > dep[y])swap(x,y);
ans += query(1,pos[x],pos[y]);
return ans;
}
int main(){
num = RD();na = RD();
for(int i = 1;i <= num;i++)col[i] = RD();
int u,v;
for(int i = 1;i <= num - 1;i++){
u = RD();v = RD();
add(u,v);
add(v,u);
}
dfs1(1,-1);
dfs2(1,1);
build(1,1,num);
char ask;
int c;
for(int i = 1;i <= na;i++){
cin>>ask;
if(ask == 'Q'){
u = RD();v = RD();
printf("%d\n",Qsum(u,v));
}
else{
u = RD();v = RD();c = RD();
uprange(u,v,c);
}
}
return 0;
}
最后,虽然dalao没帮啥忙,可是我是看大佬以前的代码查出错的,宣传一下大佬
最后当然是大家最喜欢的广告啦
题解 P2486 【[SDOI2011]染色】的更多相关文章
- luogu题解P2486[SDOI2011]染色--树链剖分+trick
题目链接 https://www.luogu.org/problemnew/show/P2486 分析 看上去又是一道强行把序列上问题搬运到树上的裸题,然而分析之后发现并不然... 首先我们考虑如何在 ...
- P2486 [SDOI2011]染色
P2486 [SDOI2011]染色 树链剖分 用区间修改线段树维护 对于颜色段的计算:sum[o]=sum[lc]+sum[rc] 因为可能重复计算,即左子树的右端点和右子树的左端点可能颜色相同 多 ...
- Luogu P2486 [SDOI2011]染色(树链剖分+线段树合并)
Luogu P2486 [SDOI2011]染色 题面 题目描述 输入输出格式 输入格式: 输出格式: 对于每个询问操作,输出一行答案. 输入输出样例 输入样例: 6 5 2 2 1 2 1 1 1 ...
- 洛谷 P2486 [SDOI2011]染色 树链剖分
目录 题面 题目链接 题目描述 输入输出格式 输入格式 输出格式 输入输出样例 输入样例: 输出样例: 说明 思路 PushDown与Update Q AC代码 总结与拓展 题面 题目链接 P2486 ...
- 洛谷 P2486 [SDOI2011]染色/bzoj 2243: [SDOI2011]染色 解题报告
[SDOI2011]染色 题目描述 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同 ...
- 洛谷P2486 [SDOI2011]染色 题解 树链剖分+线段树
题目链接:https://www.luogu.org/problem/P2486 首先这是一道树链剖分+线段树的题. 线段树部分和 codedecision P1112 区间连续段 一模一样,所以我们 ...
- luogu P2486 [SDOI2011]染色
树剖做法: 就是两个dfs+一个线段树 难度的取决基本==线段树的维护难度 所以对有点线段树基础的,树剖也不难做吧 这里操作有二 一:两点间路径染色 线段树的区间赋值操作 二:查询路径段的个数 考虑线 ...
- P2486 [SDOI2011]染色(树剖)区间覆盖+区间的连续段
https://www.luogu.org/problemnew/show/P2486 值的一看https://www.cnblogs.com/Tony-Double-Sky/p/9283262.ht ...
- 洛谷 P2486 [SDOI2011]染色(树链剖分+线段树)
题目链接 题解 比较裸的树链剖分 好像树链剖分的题都很裸 线段树中维护一个区间最左和最右的颜色,和答案 合并判断一下中间一段就可以了 比较考验代码能力 Code #include<bits/st ...
- 洛谷 P2486 [SDOI2011]染色
题目描述 输入输出格式 输入格式: 输出格式: 对于每个询问操作,输出一行答案. 输入输出样例 输入样例#1: 6 5 2 2 1 2 1 1 1 2 1 3 2 4 2 5 2 6 Q 3 5 C ...
随机推荐
- 迎来OO的曙光,总结规格的意义——OO第四次博客总结
一切都要结束了,砥砺前行~ 一.测试与正确性论证的效果差异 测试,顾名思义就是我们暴力用大量数据轰炸编写的程序的过程.日常的OO过程中,我们经常互相寻求“测试集”,正是因为测试使用特定数据对我们的功能 ...
- 奔跑吧DKY——团队Scrum冲刺阶段-Day 4
今日完成任务 谭鑫:主要解决之前存在的控件不灵敏问题,导致界面跳转不顺利. 黄宇塘:制作新的游戏背景图,对主界面图进行调整. 赵晓海:主要解决之前存在的控件不灵敏问题,导致界面跳转不顺利. 方艺雯:制 ...
- Linux下查看cpu使用率
top命令 是Linux下常用的性能 分析工具 ,能够实时显示系统 中各个进程的资源占用状况,类似于Windows的任务管理 器.下面详细介绍它的使用方法. top - 02:53:32 up 16 ...
- 第二阶段Sprint6
昨天:设置统一保存路径为内存卡,实现可以选择播放已有的视频 今天:将“录制”及“保存”整合到一起,修复出现的Bug,使之能够正常运行. 遇到的问题:感觉调的摄像头录制的画面不好,这怎么办啊?
- class 3 求数组中的最大值(单元测试)
1.问题引出: int Largest(int list[], int length) { int i,max; ; i < (length – ); i ++ ) { if(list[i] & ...
- Hibernate笔记④--一级二级缓存、N+1问题、saveorupdate、实例代码
一级缓存及二级缓存 一级缓存也是Session 缓存 一个链接用户的多次查询使用缓存 跨用户 则无缓存 hibernate自带的 get和load都会填充并利用一级缓存 二级缓 ...
- HDU 4529 郑厂长系列故事——N骑士问题 状压dp
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=4529 郑厂长系列故事--N骑士问题 Time Limit: 6000/3000 MS (Java/O ...
- sprintf函数 %6.2f
%6.2f6表示数据表示至少6位,后面的.2表示小数点后保留两位 比如2342.123415用这个表示的话,结果就是2342.12如果不足六位就会在前面补空格超过六位的话正常显示 代码例子:int m ...
- mysubmail 短信报警
https://www.mysubmail.com/chs/documents/developer/YPWD84 文本文档 官网:www.mysubmail.com 操作流程:快速接入短信 AP ...
- 高可用集群(crmsh详解)http://www.it165.net/admin/html/201404/2869.html
crmsh是pacemaker的命令行接口工具,执行help命令,可以查看shell接口所有的一级命令和二级命令,使用cd 可以切换到二级子命令的目录中去,可以执行二级子命令 在集群中的资源有四类:p ...