2777 Count Color:为什么要进制表示?+一组数据+易错点

题目大意

选择问题解决和程序设计作为可选课程,要求您解决各种问题。 在这里,我们遇到了一个新问题。

有一块很长的木板,其长度为L厘米,L是一个正整数,因此我们可以将木板均匀地划分为L个段,并用1、2,… L从左到右分别标记为1厘米 长。 现在,我们必须为板着色-只有一种颜色的一个段。 我们可以在板上执行以下两项操作:

1.“ C A B C”用颜色C为从段A到段B的板着色。

2.“ PA B”输出在A段和B段(包括)之间绘制的不同颜色的数量。

在我们的日常生活中,我们很少用语言来描述一种颜色(红色,绿色,蓝色,黄色……),因此您可以假设不同颜色T的总数非常小。 为简单起见,我们将颜色的名称表示为颜色1,颜色2,…,颜色T。开始时,木板是用颜色1绘制的。剩下的问题留给您了。

10 4 5
C 2 1 3
C 9 10 2
C 5 5 4
P 1 5
P 2 2

12 5 10
C 3 2 4
C 5 4 2
P 6 5
C 1 2 2
C 2 3 2
C 4 4 1
P 2 3
P 7 7
C 8 12 5
P 1 12

6 7 4
C 1 6 2
P 1 5
C 4 2 7
P 6 1

答案为:
3
1

2
1
1
3

1
2

思路分析

显然是一道需要用线段树的题,和2528 贴海报非常的类似。但是这道题的区间修改和区间查询是交叉进行的,我们不能也没有必要来进行离散化的操作。具体的做法:我们维护一颗线段树,线段树的节点表示该区间的颜色,我们分别考虑以下update和query函数应该如何写?

tips

  • 区间输入的时候不是按照左右端点的顺序输入的,需要判断一下谁是左端点。
  • 线段树的一贯易错点:查询的时候要把根元素的标记pushdown
  • 别用long long 用了就原地爆炸。。。。。。。。。

如果我们按照上面的思路,那就是一个普通的线段树,最多是戴上了lazy tag这样子,但是注意如果区间的颜色是这样的 1,2,3,1,2,3,1,2,3,也就是说没有任何两个相邻的位置同色,那我们下面的算法就退化成了 O ( n log n ) O(n\log n) ,会T。

#include<iostream>
#include<string.h>
#include<vector>
#include<algorithm>
using namespace std;

#define MAX 100005
#define ll long long

ll t[MAX << 2], a[MAX], vis[35], res = 0;
inline ll lc(ll p) { return p << 1; }
inline ll rc(ll p) { return p << 1 | 1; }

void pushDown(ll p) {//把父节点的颜色传下去,同时父节点回归无色
	t[lc(p)] = t[p]; t[rc(p)] = t[p];
	t[p] = 0;
}

void pushUp(ll p) {
	if (t[lc(p)] == t[rc(p)]) {//如果左右孩子的颜色相同
		t[p] = t[lc(p)]; t[lc(p)] = t[rc(p)] = 0;
	}
}

//nl,nr:要修改区间  l,r:当前区间  p:当前节点编号  k:要修改为多少
ll update(ll nl, ll nr, ll l, ll r, ll p, ll k) {
	if (nl <= l && r <= nr) { t[p] = k; return k; }
	if (t[p])pushDown(p);//有颜色的话需要推下去
	ll mid = (r + l) >> 1;
	if (nl <= mid)update(nl, nr, l, mid, lc(p), k);
	if (nr > mid)update(nl, nr, mid + 1, r, rc(p), k);
	pushUp(p);
	return t[p];
}

//返回值是节点的颜色
void query(ll nl, ll nr, ll l, ll r, ll p) {
	if (nl <= l && r <= nr && t[p] != 0) {
		if (!vis[t[p]]) vis[t[p]] = 1, res++;//统计不重复的颜色
		return;
	}
	if (t[p]) {
		pushDown(p);//如果当前节点区间太大向下查找时必须pushDown
		t[p] = t[lc(p)];//没有修改之后的颜色,所以节点本身的颜色还是存在的
	}
	ll mid = (r + l) >> 1;
	if (nl <= mid)query(nl, nr, l, mid, lc(p));
	if (nr > mid)query(nl, nr, mid + 1, r, rc(p));
}

int main() {
	ll L, T, O, m, n, k; char s;
	cin >> L >> T >> O;
	update(1, L, 1, L, 1, 1);//默认全部都是第一种颜色
	while (O--) {
		cin >> s; res = 0;
		if (s == 'P') {
			cin >> m >> n; 
			memset(vis, 0, sizeof(vis)); res = 0;
			if (m > n)swap(m, n);
			query(m, n, 1, L, 1);
			cout << res << endl;
		}
		else if (s == 'C') {
			cin >> m >> n >> k;
			if (m > n)swap(m, n);
			update(m, n, 1, L, 1, k);
		}
	}
}

如何解决上述的问题,其实线段树最基本的用法是求和不是吗?要快速的进行查询,如果我们可以维护每个区间颜色的种类那么就可以快速的进行统计了。怎么做到这一点呢?这就需要将颜色转化成二进制数,比如第一种颜色表示为1<<0,第n种颜色表示为1<<(n-1),如果我们知道左右节点颜色分别为m,n那么只需要计算m|n就可以统计一共的颜色数目了。

#include<iostream>
using namespace std;

#define MAX 110017
#define ll int

ll t[MAX << 2], a[MAX], res = 0, lazy[MAX << 2];
inline ll lc(ll p) { return p << 1; }
inline ll rc(ll p) { return p << 1 | 1; }

inline void pushDown(ll p) {//把父节点的颜色和lazy tag传下去,对原节点的颜色进行覆盖
	t[lc(p)] = t[p]; t[rc(p)] = t[p];
	lazy[lc(p)] = lazy[rc(p)] = lazy[p];
	lazy[p] = 0;
}

//统计二进制数p中1的个数
inline ll cnt1(ll p) {
	ll res = 0;
	while (p > 0) {
		if (p & 1)res++;
		p >>= 1;
	}
	return res;
}

//nl,nr:要修改区间  l,r:当前区间  p:当前节点编号  k:要修改为多少
inline void update(ll nl, ll nr, ll l, ll r, ll p, ll k) {
	if (nl <= l && r <= nr) {
		t[p] = (1 << k); 
		lazy[p] = (1 << k);
		return; //将这一段区间修改成对应的颜色
	}
	if (lazy[p])pushDown(p);//有颜色染了p但是还没下传
	ll mid = (r + l) >> 1, lcolor = 0, rcolor = 0;
	if (nl <= mid)update(nl, nr, l, mid, lc(p), k);
	if (nr > mid)update(nl, nr, mid + 1, r, rc(p), k);
	t[p] = t[lc(p)] | t[rc(p)];//更新完不忘pushUp
}

//返回值是节点的颜色
inline ll query(ll nl, ll nr, ll l, ll r, ll p) {
	if (nl <= l && r <= nr) {
		return t[p];//返回该节点的颜色情况
	}
	if (lazy[p]) {
		pushDown(p);//如果当前节点区间太大向下查找时必须pushDown
	}
	ll mid = (r + l) >> 1; ll res = 0;
	if (nl <= mid)res |= query(nl, nr, l, mid, lc(p));
	if (nr > mid)res |= query(nl, nr, mid + 1, r, rc(p));
	return res;
}

void build(ll l, ll r, ll p) {
	lazy[p] = 0;
	if (l == r) {
		t[p] = 1;
		return;
	}
	ll mid = (l + r) >> 1;
	build(l, mid, lc(p));
	build(mid + 1, r, rc(p));
	t[p] = t[lc(p)] | t[rc(p)];
}
int main() {
	ll L, T, O, m, n, k; char s;
	cin >> L >> T >> O;
	build(1, L, 1);//默认全部都是第一种颜色
	while (O--) {
		cin >> s; res = 0;
		if (s == 'P') {
			cin >> m >> n; 
			res = 0;
			if (m > n)swap(m, n);
			res = query(m, n, 1, L, 1);
			cout << cnt1(res) << endl;
		}
		else if (s == 'C') {
			cin >> m >> n >> k;
			if (m > n)swap(m, n);
			update(m, n, 1, L, 1, k - 1);
		}
	}
}
发布了211 篇原创文章 · 获赞 14 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/csyifanZhang/article/details/105308979
今日推荐