「APIO2019」路灯 (K-D Tree / 树套树 / CDQ + 树状数组)

首先想到一个简单的问题转化

对于一个询问,联通的时间是若干连续的区间$[L_i,R_i]$

所有的$L_i,R_i+1$都是关键点,即由不连通变为联通的时间 和 由联通变为不连通的时间

把答案转化为$\sum R_i+1-L_i$即可

问题转化为对于当前的操作,找到它是那些询问的关键点

如果是合并操作,被合并的两个区间之间变得联通

如果是分裂操作,裂开的两个区间之间不再联通

可以用set维护上述区间,发现每次被更新的值都是一个二维区间

算上时间这一维,问题转化为一个类 三维偏序问题,但是题限制了内存

Part1 K-D Tree

限制了内存,很容易想到直接K-D Tree,实际运行也比较优秀

注意可以把要询问的点拿出来建出K-D Tree,每次区间修改即可

时间复杂度$O(n\sqrt n)$,空间复杂度$O(n)$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define pb push_back
typedef pair <int,int> Pii;
#define mp make_pair
void cmin(int &a,int b){ ((a>b)&&(a=b)); }
void cmax(int &a,int b){ ((a<b)&&(a=b)); }

char IO;
int rd(){
int s=0;
while(!isdigit(IO=getchar()));
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return s;
}
const int N=3e5+10,INF=1e9;

int n,m,rt,col[N],opt[N],a[N],b[N];
char str[N];
set <pair <int,int> > st,tmp;
struct Node{ int x,y; } A[N];
int cmp1(Node a,Node b){ return mp(a.x,a.y)<mp(b.x,b.y); }
int cmp2(Node a,Node b){ return mp(a.y,a.x)<mp(b.y,b.x); }
int ch[N][2],lx[N],rx[N],ly[N],ry[N],s[N],t[N];
int Build(int l,int r,int d=0) {
if(l>r) return 0;
int u=(l+r)>>1;
nth_element(A+l,A+u,A+r+1,d?cmp2:cmp1);
ch[u][0]=Build(l,u-1,d^1),ch[u][1]=Build(u+1,r,d^1);
lx[u]=rx[u]=A[u].x,ly[u]=ry[u]=A[u].y;
for(int i:ch[u]) if(i) cmin(lx[u],lx[i]),cmin(ly[u],ly[i]),cmax(rx[u],rx[i]),cmax(ry[u],ry[i]);
return u;
}

void Upd(int x1,int x2,int y1,int y2,int u,int x) {
if(!u || x1>rx[u] || x2<lx[u] || y1>ry[u] || y2<ly[u]) return;
if(x1<=lx[u] && rx[u]<=x2 && y1<=ly[u] && ry[u]<=y2) return void(s[u]+=x);
if(x1<=A[u].x && A[u].x<=x2 && y1<=A[u].y && A[u].y<=y2) t[u]+=x;
for(int i:ch[u]) Upd(x1,x2,y1,y2,i,x);
}
int Que(Node x,int u,int d=0) {
if(A[u].x==x.x && A[u].y==x.y) return s[u]+t[u];
int y=ch[u][!(d?cmp2(x,A[u]):cmp1(x,A[u]))];
return Que(x,y,d^1)+s[u];
}

int main(){
n=rd(),m=rd();
scanf("%s",str+1);
rep(i,1,n) col[i]=str[i]-'0';
rep(i,1,n+1) {
int r=i;
while(col[r]) r++;
st.insert(mp(i,r));
i=r;
}
rep(i,1,m) {
scanf("%s",str+1);
if(str[1]=='t') opt[i]=1,a[i]=rd();
else opt[i]=2,a[i]=rd(),b[i]=rd(),tmp.insert(mp(a[i],b[i]));
}
n=0;
for(auto it:tmp) A[++n]=(Node){it.first,it.second};
rt=Build(1,n);
rep(i,1,m) {
if(opt[i]==1) {
int x=a[i];
if(col[x]) {
auto it=st.upper_bound(mp(x,INF)); it--;
int l=it->first,r=it->second;
st.erase(it);
st.insert(mp(l,x)),st.insert(mp(x+1,r));
Upd(l,x,x+1,r,rt,i);
} else {
auto it=st.upper_bound(mp(x,INF)),tmp=it; it--;
int l=it->first,r=tmp->second;
st.erase(it),st.erase(tmp);
st.insert(mp(l,r)),Upd(l,x,x+1,r,rt,-i);
}
col[x]^=1;
} else {
int ans=Que((Node){a[i],b[i]},rt);
auto it=st.upper_bound(mp(a[i],INF)); it--;
if(it->second>=b[i]) ans+=i;
printf("%d\n",ans);
}
}
}

Part2 树套树(没有代码)

由于已知查询的节点,树套树的内存可以优化到$O(n\log n)$

把要询问的点拉出来,每次询问在第二维中有$\log n$次单点查询,所以需要被查询的位置一共只有$n\log n$个

把这些会被查询的位置拿出来建成$n$棵静态的线段树,更新就直接在这些静态的线段树上区间更新即可

时间复杂度$O(n\log ^2 n)$,空间复杂度$O(n\log n)$

Part3 CDQ+树状数组

是常规写法,不会被限制内存

CDQ一维,排序一维,树状数组一维,参见三维偏序