luoguP2161 [SHOI2009]会场预约
好久没打线段树了...为不会做找借口
https://www.luogu.org/problem/P2161
题意:
大概是说,一开始全空序列,之后开始安排招待会议,输入有q个操作,分两种:
A l r
: 将[l , r]占了,以前在 【l , r】的会议取消,并输出取消会议的个数
B
: 输入当前存在的会议
首先我们需要理清问题的实质:这里我们将每个会议看成一个颜色
操作A实质:查询一段区间内有多少种颜色,再清空这区间里的每种颜色所属的区域,再在这一区间填上另一种颜色
其实就是一个删除操作难想,其实不用真正的删除,用一个del[]记录每种颜色是否被删,然后在更新A,B操作答案的时候判一下就行了
因题而异吧
#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
const int MAX = 200000+99;
int q;
struct tree{
int is, set;
//is:区间o是否为一种颜色
}tr[MAX<<2];
int del[MAX], del_n, ans;
//ans表示还剩颜色数,del_n表示每次删除颜色的数目,del保存该颜色是否已经被清除
void build(int o, int l, int r) {
tr[o].is = 1, tr[o].set = 0;
if(l == r) return ;
int mid = (l+r)>>1;
build(o<<1, l, mid);
build(o<<1|1, mid+1, r);
}
void pushdown(int o) {
tr[o].is = 0;//下放后该区间颜色不一 (要先写这个!!,因为可能它没有set,但是却为不同颜色
if(tr[o].set == 0) return ;
tr[o<<1].set = tr[o<<1|1].set = tr[o].set;
tr[o<<1].is = tr[o<<1|1].is = 1;
tr[o].set = 0;
}
void query(int o, int l, int r, int ql, int qr, int k) {
if(tr[o].is) {
if(tr[o].set!=0 && !del[tr[o].set]) --ans, ++del_n;//注意判无色
del[tr[o].set] = 1;
tr[o].set = k/*, tr[o].is = 1*/;
return ;
}
int mid = (l+r)>>1;
if(ql <= mid) query(o<<1, l, mid, ql, qr, k);
if(mid < qr) query(o<<1|1, mid+1, r, ql, qr, k);//找到区间中颜色唯一的tr,并修改
tr[o].is = 1, tr[o].set = k;
}
void optset(int o, int l, int r, int ql, int qr, int k) {
if(ql <= l && r <= qr) {query(o, l, r, ql, qr, k); return;}//找到要查询并修改的区间
int mid = (l+r)>>1;
pushdown(o);
if(ql <= mid) optset(o<<1, l, mid, ql, qr, k);
if(mid < qr) optset(o<<1|1, mid+1, r, ql, qr, k);
}
struct tmp{
int l, r;
char ab;
}cmd[MAX];
int st = MAX, ed = -MAX;
int main() {
scanf("%d",&q);
for(int i = 1; i <= q; i++) {
cin>>cmd[i].ab;
if(cmd[i].ab == 'A') {
scanf("%d%d",&cmd[i].l, &cmd[i].r);
st = min(cmd[i].l, st), ed = max(ed, cmd[i].r);
}
}
build(1, st, ed);
int cnt = 0;//颜色序数
for(int i = 1; i <= q; i++) {
if(cmd[i].ab == 'A') {
++cnt, del_n = 0, ++ans;
optset(1, st, ed, cmd[i].l, cmd[i].r, cnt);
printf("%d
",del_n);
} else printf("%d
",ans);
}
return 0;
}