正解是教我们学习set的运用,但是这里想介绍一下我憨憨的线段树做法,同样是O(nlogn)的复杂度,只是常数比set做法大不少,标程21ms,我的时间为175ms,大概是八九倍的常数(有点鸡肋看起来)。对了因为数字最大值能到1e9,线段树开不下,但是因为我们不需要线段的具体大小只要知道他们每个点的相对位置就行,所以可以离散化方便建树。
这题就是判断是否生成新线段,以及合并其他线段两个操作。
判断
我们容易推知,当当前线段左端点在空白区域内才有可能生成新线段。这一步的操作的前提是每一次在更新时将区域内打上标记,我采用的方式是用线段树实现区域内+1。然后每次查询左端点值是否为0即可判断有没有可能生成独立新线段。
但是如果当前线段覆盖了其他线段要怎么判断呢?我们同样观察左端点,如果区间内包含已经出现过的左端点,则独立线段总数就应当 - -,这个可以随便手推。而我们此时则又要统计左端点的数量,统计线段内数量线段树也能完成,意思是要建两颗独立的树?嗯哼,结构体一挂,就有了a树b树,然后更新和查询就都是一般操作了。
合并
判断讲完了其实大致思路已经都完了,这里是提一下合并时,要注意将区间内已经存在过的左端点数量清零,他们现在已经不是有效的左端点了,不能被下一次判断计入。我交题时直接搞了隔壁A题的线段树乘法过来,区间内清零就是× 0 嘛。
AC代码 :
#include<bits/stdc++.h>
#define maxn 200005
#define maxm 200005
#define FOR(a, b, c) for(int a=b; a<=c; a++)
#define hrdg 1000000007
#define inf 2147483647
#define llinf 9223372036854775807
#define ll long long
#define pi acos(-1.0)
#define ls p<<1
#define rs p<<1|1
using namespace std;
int n, m, l[maxn], r[maxn], T, flag, ans, cnt;
int pre_lsh[maxn], l1[maxn], r1[maxn];
inline int read(){
char c=getchar();long long x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
struct Tree{ //结构体挂两棵树,一棵判断区间是否被覆盖,一棵用于更新和统计左端点数量
int tr[maxn<<1], sum[maxn<<1], mul[maxn<<1];
void pushup(int p){tr[p] = tr[ls] + tr[rs];}
void build(int p, int l, int r){
sum[p] = 0;
mul[p] = 1;
if(l == r) return;
int mid = (l+r)>>1;
build(ls, l, mid);
build(rs, mid+1,r);
pushup(p);
}
void load(int p, int l, int r, int mulk, int sumk){
tr[p] = tr[p] * mulk + (r-l+1) * sumk;
sum[p] = sum[p] * mulk + sumk;
mul[p] = mul[p] * mulk;
}
void pushdown(int p, int l, int r){
int mid = (l+r)>>1;
load(ls, l, mid, mul[p], sum[p]);
load(rs, mid+1, r, mul[p], sum[p]);
mul[p] = 1;
sum[p] = 0;
}
void update1(int nl, int nr, int l, int r, int p, int k){ //乘法
if(nl<=l && nr>=r)
{
load(p, l, r, k, 0);
return;
}
pushdown(p, l, r);
int mid = (l+r)>>1;
if(nl <= mid)
update1(nl, nr, l, mid, ls, k);
if(nr >= mid+1)
update1(nl, nr, mid+1, r, rs, k);
pushup(p);
}
void update2(int nl, int nr, int l, int r, int p, int k){ //加法
if(nl<=l && nr>=r)
{
load(p, l, r, 1, k);
return;
}
pushdown(p, l, r);
int mid = (l+r)>>1;
if(nl <= mid)
update2(nl, nr, l, mid, ls, k);
if(nr >= mid+1)
update2(nl, nr, mid+1, r, rs, k);
pushup(p);
}
int query(int nl, int nr, int l, int r, int p){
int ret = 0;
if(nl <= l && nr >= r)
return tr[p];
pushdown(p, l, r);
int mid = (l+r)>>1;
if(nl <= mid)
ret += query(nl, nr, l, mid, ls);
if(nr >= mid+1)
ret += query(nl, nr, mid+1, r, rs);
return ret;
}
}a, b;
void discret() //离散化,我不信你还能卡我
{
sort(pre_lsh+1, pre_lsh+1+T*2);
n = unique(pre_lsh+1, pre_lsh+1+T*2) - (pre_lsh+1);
FOR(i, 1, T)
{
l[i] = lower_bound(pre_lsh+1, pre_lsh+1+n, l1[i]) - pre_lsh;
r[i] = lower_bound(pre_lsh+1, pre_lsh+1+n, r1[i]) - pre_lsh;
}
n++;
}
//离散化复杂度2*O(nlogn),比较而言set不需要,所以set很快
int main(){
T = read();
FOR(i, 1, T){
l1[i] = read();
r1[i] = read();
pre_lsh[i*2-1] = l1[i];
pre_lsh[i*2] = r1[i];
}
discret(); //离散化
a.build(1, 1, n);
b.build(1, 1, n);
FOR(i, 1, T)
{
flag = a.query(l[i], l[i], 1, n, 1);
if(flag == 0) ans++;
if(l[i] != r[i])
cnt = b.query(l[i]+1, r[i], 1, n, 1);
else
cnt = 0;
if(b.query(l[i], l[i], 1, n, 1))
flag = 0; //用于后面更新左端点的判断
ans -= cnt;
printf("%d ", ans);
a.update2(l[i], r[i], 1, n, 1, 1);
b.update1(l[i], r[i], 1, n, 1, 0); //区间内不应该有左端点了,清零
if(flag == 0)
b.update2(l[i], l[i], 1, n, 1, 1);
//我们可以看到在一次循环里我们最多会有6次线段树查询或更新的O(logn)操作,这也是线段树慢的原因
}
return 0;
}