【题解】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]);
}
}
}