[SDOI2011] 染色

题目描述

给定一个 n n n 个节点的无根树,共有 m m m 个操作,操作分为两种:

  1. 将节点 a a a 到节点 b b b 的路径上的所有点(包括 a a a b b b)都染成颜色 c c c
  2. 询问节点 a a a 到节点 b b b 的路径上的颜色段数量。

颜色段的定义是极长的连续相同颜色被认为是一段。例如 112221 由三段组成:112221

输入格式

输入的第一行是用空格隔开的两个整数,分别代表树的节点个数 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 opC,则代表本次操作是一次染色操作,在一个空格后有三个用空格隔开的整数 a , b , c a,b,c a,b,c,代表将 a a a b b b 的路径上所有点都染成颜色 c c c
  • o p op opQ,则代表本次操作是一次查询操作,在一个空格后有两个用空格隔开的整数 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 1n,m105 1 ≤ w i , c ≤ 1 0 9 1 \le w_i,c \le 10^9 1wi,c109 1 ≤ a , b , u , v ≤ n 1 \le a,b,u,v \le n 1a,b,u,vn o p op op 一定为 CQ,保证给出的图是一棵树。

题解


相当于树链剖分 + 常见数字那道题的线段树。


树剖部分:

最基础的模板,如有不会请移步 [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;
}

注意事项:

  1. 检查线段树传入的区间是否写错。
  2. pushdown和pushup的信息是否全部处理完。
  3. 合并答案的顺序。
  4. 本题不需要开 long long
  5. 线段树开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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值