【hdu 6089】Rikka with Terrorist
题意
有一个 \(n\times m\) 的二维网格,其中有 \(k\) 个禁止点。
有 \(q\) 组询问,每组询问为给一个点,求有多少个矩形以这个点为一角且不包含禁止点。
\(n,m,k,q\le 10^5\)
sol
zjt 是怎么认为这是道李超树题的……难道只是因为看到了官方题解吗?
这题不难,但是太恶心了我琢磨了三小时,关键是没选手写题解,官方题解还写李超树误导人
不过官方题解除了最后一段最后一句外,其余内容还是可以借鉴的:
问题相当于平面中有若干障碍点,询问以某一个点为四个角之一的不包含障碍点的矩形有多少个。
对每一组询问,维护数组 \(U,D\),\(U_i\) 表示所有在询问点上方的障碍点中,横坐标为 \(i\) 的纵坐标最小值,\(D_i\) 表示所有在询问点下方的障碍点中,横坐标在 \(i\) 的纵坐标最大值。那么枚举矩形横坐标范围的另一端 \(j\),满足条件的矩形有 \(\min_{k∈[i,j]} U_k - \max_{k∈[i,j]} D_k − 1\) 个。
离线,按照纵坐标从小到大枚举询问,因为 \(U\) 和 \(D\) 只会修改 \(O(K)\) 次,所以可以用线段树维护。之后要考虑的就是求 \(\sum\limits_{j=1}^{n} \min_{k∈[i,j]} U_k\) 和 \(\sum\limits_{j=1}^{n} \max_{k∈[i,j]} D_k\)。这个是李超线段树的经典问题,可以在 \(O(n\log^2n)\) 的时间复杂度内解决。
离线是个很好的思路。
然后我们再思考一下,发现官方题解是把原问题拆成了上下两部分求解,但我们可以把原问题拆成四个象限求解,每次把坐标系旋转 \(90°\),然后对同一个象限求解(可以是任意象限,只要四次求解的象限相同就行)。本文处理的是最好维护的第三象限。
把所有点以横坐标为第一关键字,纵坐标为第二关键字,从小到大排序。
考虑一次性从下往上处理一列的所有点。一个询问点在某一个象限的答案大概就是图中的紫色区域:
点表示询问点,十字表示禁止点。
则图中紫色区域就是红色询问点点在一个象限的答案。
则我们需要动态维护当前询问点所在列的下方离他最近的禁止点的位置。
然后求出红色矩形的面积,减去左边那些禁止点组成的类似于“上凸包”的面积。
“上凸包”的面积就是每个前缀的 所有后缀区间最大值的和。
问题变成了如何用线段树动态维护 以每个点为结尾的所有后缀区间最大值的和。
线段树上每个节点用一个变量 \(sum\) 记录该区间所有后缀区间最大值的和。不难发现在叶子结点处,这个值很好得到,我们考虑怎么把它从两个儿子合并到父亲。
不难发现,右儿子的 \(sum\) 可以直接加到父亲上,而左儿子由于记得是以父亲区间中点为结尾的 \(sum\),而我们需要左儿子以父亲区间右端点(即右儿子右端点)为后缀结尾,所以我们重新计算一下右儿子对左儿子的影响,得到左儿子对父亲的贡献。
不难发现,把左儿子的所有后缀的右端点 延长到右儿子的右端后,右儿子的最大值会对左儿子的某个后缀区域造成影响。那这个后缀区域的左端点是哪呢?设右儿子最大值为 \(x\),显然就是左儿子从右往左数第一个 \(\ge x\) 的位置。这个位置右边的数都会因后缀右端点延长,要与 \(x\) 取 \(\max\) 而被推平成 \(x\),这个位置及其左边的数则都不会受影响。
所以我们在线段树的每个节点再维护一个区间 \(\max\)。在 \(pushup\) 更新父亲的 \(sum\) 时,在左子树内进行二分,找到最右边的 \(\ge x\) 的位置,若往右子树走则累加 \(sum[cur]-sum[rson]\)(就是左子树的答案),若往左子树走则累加 \(x\times (r-mid)\)(就是右子树被推平成 \(x\) 了)。
查询时要查询一个区间的 \(sum\) 和。对于该区间在线段树上拆得的 \(\log\) 个区间,用类似于 \(pushup\) 的方法从右往左合并这些区间,显然合并两个区间并不要求这两个区间等长。
注意在处理一列时,先做完这列所有点的查询,再用这列所有点更新线段树。
还有一个需要注意的细节是统计一个询问点的答案时,不要考虑它的行左,因为它的列下已经被考虑上了,转四次坐标系之后会发现询问点所在的行左、行右、列上、列下都恰好被算了一次,这样就避免处理了重复计算的问题。
总之就是个快乐线段树题。时间复杂度 \(O(n\log^2{n})\),因为每次 \(pushup\) 都要在子树内做一次线段树二分。
#include<bits/stdc++.h>
#define ll long long
#define N 100010
#define lc o<<1
#define rc o<<1|1
#define fi first
#define se second
using namespace std;
typedef pair<ll,int> pr;
inline int read(){
int x=0; bool f=1; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
if(f) return x;
return 0-x;
}
int nn,mm,k,q;
struct Point{
int x,y,id;
Point(){}
Point(int a, int b, int c):x(a), y(b), id(c){}
inline bool operator < (const Point &a)const{
return x!=a.x ? x<a.x : y<a.y;
}
}a[N<<1];
namespace SegTree{
struct Tree{int mx; ll sum;}tr[N<<2];
void build(int o, int l, int r){
tr[o].mx=tr[o].sum=0;
if(l==r) return;
int mid=l+r>>1;
build(lc,l,mid), build(rc,mid+1,r);
}
ll go(int o, int l, int r,int v){
if(l==r) return max(tr[o].mx,v);
int mid=l+r>>1;
if(tr[rc].mx>=v) return tr[o].sum-tr[rc].sum+go(rc,mid+1,r,v);
else return go(lc,l,mid,v)+(ll)v*(r-mid);
}
inline void pushup(int o, int l, int r){
int mid=l+r>>1;
tr[o].sum = tr[rc].sum + go(lc,l,mid,tr[rc].mx);
tr[o].mx = max(tr[lc].mx, tr[rc].mx);
}
void upd(int o, int l, int r, int x, int v){
if(l==r){tr[o].mx=tr[o].sum=v; return;}
int mid=l+r>>1;
if(x<=mid) upd(lc,l,mid,x,v);
else upd(rc,mid+1,r,x,v);
pushup(o,l,r);
}
pr query(int o, int l, int r, int L, int R, int v){
if(L<=l && r<=R) return pr(go(o,l,r,v), max(v,tr[o].mx));
int mid=l+r>>1;
if(R<=mid) return query(lc,l,mid,L,R,v);
if(mid<L) return query(rc,mid+1,r,L,R,v);
pr b = query(rc,mid+1,r,L,R,v);
pr a = query(lc,l,mid,L,R,b.se);
return pr(a.fi+b.fi, a.se);
}
}using namespace SegTree;
void rotate(int n){
for(int i=1; i<=n; ++i){
int x=mm-a[i].y+1, y=a[i].x;
a[i].x=x, a[i].y=y;
}
swap(nn,mm);
}
int now[N];
ll ans[N];
void solve(int n){
rotate(n);
build(1,1,mm);
sort(a+1,a+n+1);
memset(now,0,sizeof now);
for(int i=1,lst=0; i<=n; i=lst+1){
if(a[i].x!=a[i-1].x){
lst=i;
while(a[lst].x==a[lst+1].x) ++lst;
int l=0;
for(int j=i; j<=lst; ++j)
if(!a[j].id) l=a[j].y;
else if(l+1<=a[j].y-1)
ans[a[j].id] += (ll)a[j].x*(a[j].y-l-1) - query(1,1,mm,l+1,a[j].y-1,now[a[j].y]).fi;
for(int j=i; j<=lst; ++j)
if(!a[j].id)
upd(1,1,mm,a[j].y,a[j].x),
now[a[j].y]=a[j].x;
}
}
}
int main(){
nn=read(), mm=read(), k=read(), q=read();
int x,y;
for(int i=1; i<=k; ++i){
x=read(), y=read();
a[i]=Point(x,y,0);
}
for(int i=1; i<=q; ++i){
x=read(), y=read();
a[k+i]=Point(x,y,i);
}
for(int i=1; i<=4; ++i) solve(k+q);
for(int i=1; i<=q; ++i) printf("%lld\n",ans[i]+1);
return 0;
}
/*
19 19 20 19
9 11
12 11
8 3
10 2
11 2
18 8
10 6
16 11
13 9
13 8
8 7
2 6
5 7
7 18
6 5
16 15
17 14
15 1
2 4
3 3
10 10
15 17
8 17
6 9
16 2
5 15
17 4
4 3
4 14
9 6
19 16
14 4
7 11
14 15
4 1
14 14
3 11
9 19
15 15
*/