【题解】Codeforces Round #771 (Div. 2)

E

如果颜色修改只是单点的,我们可以想到这么一个做法:
对于add操作,用一个数组lazy[c]记录对颜色c加过的所有值之和
对于query操作,直接用a[i]+lazy[col[i]]
当改颜色c'→c时,我们令a[i]+=lazy[c'],因为现在不加以后就没机会加了,然后col[i]=c;但是我们会发现下次查询时会算重,即多算了lazy[c]之前的贡献,所以此时先扣除即可,即a[i]-=lazy[c]

对于区间颜色修改,我们仍然考虑同样的做法,这时需要注意到一个“区间颜色覆盖”的性质:
将连续一段相同颜色的视作一个颜色块,一次区间覆盖后最多使颜色块的个数+2

回到题目,对于一次区间颜色修改,按原来的方式我们需要一个一个地做单点修改操作,现在我们一块一块地做,用线段树做到单次log
修改掉一个就少一个颜色块,所以我们所有修改的次数不超过区间覆盖次数*2
我们用一个set来维护这些颜色块,每次只有恰好跨过区间端点的两个区间需要先被拆分;具体实现见代码,参考了别人的set的写法,大概可以当个板子了

#include <set>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const int MAXN = 1000006;
int N, Q; ll lazy[MAXN];
struct node {
	int l, r, c; // [l, r] 
	node (int _l=0, int _r=0, int _c=0) {
		l = _l, r = _r, c = _c;
	}
	bool operator < (const node &o) const {
		return l==o.l ? r< o.r : l< o.l;
	}
};
set<node> S;
struct segmentTree {...} ST;
void split(int pos)
{
	if (pos< 1 || pos> N) return;
	auto it = S.lower_bound(node(pos)); // (l=pos,r=0)<(l=pos,r>0)<(l>pos,)
	if (it!=S.end() && it->l==pos) return;
	it--;
	int l = it->l, r = it->r, c = it->c;
	S.erase(it), S.insert(node(l, pos-1, c)), S.insert(node(pos, r, c));
}
void print()
{
	for (auto it:S) printf("(%d, %d), %d\n", it.l, it.r, it.c);
}
int main()
{
	scanf("%d%d", &N, &Q);
	S.insert(node(1, N, 1)), ST.build(1, 1, N);
	char opt[10]; int l, r, c; ll x;
	for (; Q; Q--) {
		scanf("%s", opt);
		if (opt[0]=='C') {
			scanf("%d%d%d", &l, &r, &c);
			split(l), split(r+1); // 划分区间两端可能跨过端点的两个块
			auto it = S.lower_bound(node(l));
			while (it!=S.end() && it->l<=r) {
				ST.updatea(1, 1, N, it->l, it->r, lazy[it->c]-lazy[c]);
				it = S.erase(it);
			}
			S.insert(node(l, r, c)), ST.updatec(1, 1, N, l, r, c);
		//	print();
		} else if (opt[0]=='A') {
			scanf("%d%lld", &c, &x);
			lazy[c] += x;
		} else {
			scanf("%d", &l);
			ll a = ST.querya(1, 1, N, l);
			int c = ST.queryc(1, 1, N, l);
			printf("%lld\n", a+lazy[c]);
		}
	}
}

F

posted @ 2022-02-16 19:06  zrkc  阅读(82)  评论(0编辑  收藏  举报