题目描述
给定一个 n n n 个节点的无根树,共有 m m m 个操作,操作分为两种:
- 将节点 a a a 到节点 b b b 的路径上的所有点(包括 a a a 和 b b b)都染成颜色 c c c。
- 询问节点 a a a 到节点 b b b 的路径上的颜色段数量。
颜色段的定义是极长的连续相同颜色被认为是一段。例如 112221
由三段组成:11
、222
、1
。
输入格式
输入的第一行是用空格隔开的两个整数,分别代表树的节点个数 n n n 和操作个数 m m m。
第二行有 n n n 个用空格隔开的整数,第 i i i 个整数 w i w_i wi 代表结点 i i i 的初始颜色。
第 3 3 3 到第 n + 1 n + 1 n+1 行,每行两个用空格隔开的整数 u , v u, v u,v,代表树上存在一条连结节点 u u u 和节点 v v v 的边。
第 n + 2 n + 2 n+2 到第 n + m + 1 n + m + 1 n+m+1 行,每行描述一个操作,其格式为:
每行首先有一个字符 o p op op,代表本次操作的类型。
- 若
o
p
op
op 为
C
,则代表本次操作是一次染色操作,在一个空格后有三个用空格隔开的整数 a , b , c a,b,c a,b,c,代表将 a a a 到 b b b 的路径上所有点都染成颜色 c c c。 - 若
o
p
op
op 为
Q
,则代表本次操作是一次查询操作,在一个空格后有两个用空格隔开的整数 a , b a,b a,b,表示查询 a a a 到 b b b 路径上的颜色段数量。
输出格式
对于每次查询操作,输出一行一个整数代表答案。
样例
样例输入1:
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
样例输出1:
3
1
2
数据范围
对于
100
%
100\%
100% 的数据,
1
≤
n
,
m
≤
1
0
5
1 \le n, m \le 10^5
1≤n,m≤105,
1
≤
w
i
,
c
≤
1
0
9
1 \le w_i,c \le 10^9
1≤wi,c≤109,
1
≤
a
,
b
,
u
,
v
≤
n
1 \le a,b,u,v \le n
1≤a,b,u,v≤n,
o
p
op
op 一定为 C
或 Q
,保证给出的图是一棵树。
题解
相当于树链剖分 + 常见数字那道题的线段树。
树剖部分:
最基础的模板,如有不会请移步 [HAOI2015] 树上操作。
这里我的写法有些不同,记录了每个节点所属的链的编号和每个链上的最小节点。
void dfs1(int x, int fa){
siz[x] = 1;
ft[x] = fa;
dep[x] = dep[fa] + 1;
son[x] = -1;
int maxn = 0, maxm = -1;
for(auto i : v[x]){
if(i == fa) continue;
dfs1(i, x);
siz[x] += siz[i];
if(siz[i] > maxn){
maxn = siz[i];
maxm = i;
}
}
son[x] = maxm;
}
void dfs2(int x, int fa, int tp){
dfn[x] = ++ tim;
bl[x] = tp;
if(son[x] != -1) dfs2(son[x], x, tp);
for(auto i : v[x]){
if(i == fa) continue;
if(i != son[x]){
top[++ cnt] = i;
dfs2(i, x, cnt);
}
}
}
线段树:
存储
存左右边界,标记,区间中的颜色段数量,左右边界的颜色。
pushdown
如果当前节点有标记,就下传到左右节点。
void downdata(int x){
if(tree[x].add == -1) return;
tree[x << 1].add = tree[x << 1 | 1].add = tree[x].add;//下传标记
tree[x << 1].sum = tree[x << 1 | 1].sum = 1;
tree[x << 1].lsum = tree[x << 1].rsum = tree[x].add;
tree[x << 1 | 1].lsum = tree[x << 1 | 1].rsum = tree[x].add;
tree[x].add = -1;//取消标记
}
pushup
合并左右区间。
void updata(int x){
tree[x].lsum = tree[x << 1].lsum;
tree[x].rsum = tree[x << 1 | 1].rsum;
tree[x].sum = tree[x << 1].sum + tree[x << 1 | 1].sum;
if(tree[x << 1].rsum == tree[x << 1 | 1].lsum){//若左边区间的右端点与右边区间的左端点颜色相同,颜色段数量减一
-- tree[x].sum;
}
}
建树
普通的建树,注意节点的颜色,w[dfn[i]] = a[i]
。
void build(int bh, int l, int r){
tree[bh].tl = l, tree[bh].tr = r, tree[bh].add = -1;
if(l == r){
tree[bh].lsum = tree[bh].rsum = w[i];
tree[bh].sum = 1;
return;
}
int mid = (l + r) >> 1;
build(bh << 1, l, mid);
build(bh << 1 | 1, mid + 1, r);
updata(bh);
}
区间修改
void modifiy(int bh, int l, int r, int d){
if(tree[bh].tr < l || tree[bh].tl > r) return;
if(tree[bh].tl >= l && tree[bh].tr <= r){//包含区间
tree[bh].add = d;
tree[bh].lsum = d, tree[bh].rsum = d;
tree[bh].sum = 1;
return;
}
downdata(bh);
modifiy(bh << 1, l, r, d);
modifiy(bh << 1 | 1, l, r, d);
updata(bh);
}
区间查询
用 lc
记录最左边的颜色,rc
记录最右边的颜色,方便后面的查询。
int query(int bh, int l, int r){
if(tree[bh].tl >= l && tree[bh].tr <= r){
if(tree[bh].tl == l){
lc = tree[bh].lsum;
}
if(tree[bh].tr == r){
rc = tree[bh].rsum;
}
return tree[bh].sum;
}
downdata(bh);
int mid = (tree[bh].tl + tree[bh].tr) >> 1;
//注意不要把query传的参写错了
if(l > mid){
return query(bh << 1 | 1, l, r);
}
if(r <= mid){
return query(bh << 1, l, r);
}
int t1 = query(bh << 1, l, r), t2 = query(bh << 1 | 1, l, r);
if(tree[bh << 1].rsum == tree[bh << 1 | 1].lsum){
-- t1;
}
return t1 + t2;
}
树上操作
树上修改
void change(int t1, int t2, int d){
while(bl[t1] != bl[t2]){
if(dep[top[bl[t1]]] < dep[top[bl[t2]]]) swap(t1, t2);
modifiy(1, dfn[top[bl[t1]]], dfn[t1], d);
t1 = ft[top[bl[t1]]];
}
if(dfn[t1] > dfn[t2]) swap(t1, t2);
modifiy(1, dfn[t1], dfn[t2], d);
}
树上查询
int ask(int t1, int t2){
int ans = 0, p1 = 0, p2 = 0;
while(bl[t1] != bl[t2]){
if(dep[top[bl[t1]]] < dep[top[bl[t2]]]){
swap(t1, t2);
swap(p1, p2);
}
ans += query(1, dfn[top[bl[t1]]], dfn[t1]);
if(p1 == rc) -- ans;//合并当前的区间和之前求出来的区间的颜色段
p1 = lc;
t1 = ft[top[bl[t1]]];
}
if(dfn[t1] > dfn[t2]){
swap(t1, t2);
swap(p1, p2);
}
ans += query(1, dfn[t1], dfn[t2]);
if(lc == p1) -- ans;
if(rc == p2) -- ans;
return ans;
}
注意事项:
- 检查线段树传入的区间是否写错。
- pushdown和pushup的信息是否全部处理完。
- 合并答案的顺序。
- 本题不需要开
long long
。 - 线段树开4倍空间。
代码:
void dfs1(int x, int fa);
void dfs2(int x, int fa, int tp);
void downdata(int x);
void updata(int x);
void build(int bh, int l, int r);
void modifiy(int bh, int l, int r, int d);
int query(int bh, int l, int r);
void change(int t1, int t2, int d);
int ask(int t1, int t2){
int ans = 0, p1 = 0, p2 = 0;
while(bl[t1] != bl[t2]){
if(dep[top[bl[t1]]] < dep[top[bl[t2]]]){
swap(t1, t2);
swap(p1, p2);
}
ans += query(1, dfn[top[bl[t1]]], dfn[t1]);
if(p1 == rc) -- ans;
p1 = lc;
t1 = ft[top[bl[t1]]];
}
if(dfn[t1] > dfn[t2]){
swap(t1, t2);
swap(p1, p2);
}
ans += query(1, dfn[t1], dfn[t2]);
if(lc == p1) -- ans;
if(rc == p2) -- ans;
return ans;
}
int main(){
scanf("%d %d", &n, &q);
for(int i = 1; i <= n; ++ i){
scanf("%d", &a[i]);
}
for(int i = 1; i < n; ++ i){
int x, y;
scanf("%d %d", &x, &y);
v[x].push_back(y);
v[y].push_back(x);
}
dfs1(1, 0);
cnt = 1;
top[1] = 1;
dfs2(1, 0, 1);
for(int i = 1; i <= n; ++ i){
w[dfn[i]] = a[i];
}
build(1, 1, n);
for(int i = 1; i <= q; ++ i){
char op;
int x, y, z;
cin >> op;
if(op == 'C'){
scanf("%d %d %d", &x, &y, &z);
change(x, y, z);
}
else{
scanf("%d %d", &x, &y);
printf("%d\n", ask(x, y));
}
}
return 0;
}
题外话
机房某位大佬用珂朵莉树写的,只写了120行。
调题警告。
我第一次写的查询函数:
//相当于左右两边合成完了再来和中间的合并
//bl 是该节点所属链的编号,top 是链的最上面的节点
int ask(int t1, int t2){
node p1, p2;
int fl1 = -1, fl2 = -1;
while(bl[t1] != bl[t2]){
if(dep[top[bl[t1]]] <= dep[top[bl[t2]]]){
node pr = query(1, dfn[top[bl[t2]]], dfn[t2]);
if(fl2 == -1){
p2 = pr;
fl2 = 1;
}
else{
p2 = updata(pr, p2);
}
t2 = ft[top[bl[t2]]];
}
else{
node pr = query(1, dfn[top[bl[t1]]], dfn[t1]);
if(fl1 == -1){
p1 = pr;
fl1 = 1;
}
else{
p1 = updata(pr, p1);
}
t1 = ft[top[bl[t1]]];
}
}
if(dfn[t1] <= dfn[t2]){
node pr = query(1, dfn[t1], dfn[t2]);
if(fl1 != -1 && fl2 != -1){
return updata(updata(p1, pr), p2).sum;
}
if(fl1 != -1){
return updata(p1, pr).sum;
}
if(fl2 != -1){
return updata(pr, p2).sum;
}
return pr.sum;
}
else{
node pr = query(1, dfn[t2], dfn[t1]);
if(fl1 != -1 && fl2 != -1){
return updata(updata(p2, pr), p1).sum;
}
if(fl2 != -1){
return updata(p2, pr).sum;
}
if(fl1 != -1){
return updata(pr, p1).sum;
}
return pr.sum;
}
}
然后就不知道为什么挂了。
开始调题,调不出来,于是换了一种计算的方法就过了(也就是上面的方法)。
如果有人知道我哪里错了,请在评论区指出错误。
thanks