最近看我好久没写博客了是不是。。。
其原因是因为我最近期末复习,比较忙,周一到周五平均睡眠时间晚上12:00才能完成刷题任务,更别说写博客了。只有周六周日能抽出点时间写点。。。
从今天开始,我不会再写所有我做过的题了(太多了,写题解的速度肯定没有刷题的速度快)。所以我就选一些比较有意思的,有意义的,扩展思维的,写写博客。(或者是那些我WA了几百遍的。。。
好吧。正式开始
题意简述
给定一棵带点权树,要支持两种操作:
- 路径赋值:从 u u u到 v v v的路径上的点权都变成一个指定的数 c c c
- 路径计算:计算 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
<
=
1
e
5
)
(n,m<=1e5)
(n,m<=1e5)
接下来一行
n
n
n个数,表示
n
n
n个节点的初始点权。
接下来
n
−
1
n-1
n−1行每行一个
u
,
v
u,v
u,v,表示
u
,
v
u,v
u,v之间有连边。
接下来
m
m
m行,每行是这样的形式:
- Q a b Q\ a\ b Q a b 对 a , b a,b a,b进行路径计算操作
- C a b c C\ a\ b\ c C a b c 对 a , b a,b a,b进行路径赋值操作,值赋为 c ( 0 < = c < = 1 e 9 ) c(\red{0<=}c<=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 < = 1 e 9 \red{c<=1e9} c<=1e9。我艹!
为啥这个东西毒瘤?大概是经过了这样一个心路历程:
- 设 s s s表示区间的段数量
- 然后 s = s= s=左右儿子的 s s s之和
然后我们很容易举出反例。。。如(其中
∣
|
∣是左右儿子的分割线):
1
2
∣
2
3
\begin{matrix} 1 & 2 & \red{|} & 2 & 3 \end{matrix}
12∣23
然后左右儿子相加是
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
−
>
L
C
A
u->LCA
u−>LCA和
L
C
A
−
>
v
LCA->v
LCA−>v。其中
u
−
>
L
C
A
u->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;
}