hdu6089 Rikka with Terrorist
\(n\times m\) 网格图,给一个指定的点集 \(S\),\(q\) 次询问(\(n,m,q,|S|\le 10^5\)),给定一个点 \((x,y)\),问有多少个目标点 \((x',y')\) 满足
图都是手绘的,造成视觉障碍概不负责。
一个经典问题,目标点大概是这种形式(黑框内):
我们只考虑左上部分,剩下可以通过顺时针转四次 \(90^\circ\)(见代码)用同样的方式处理掉。注意左上指的是 \(x'<x,y'\le y\),这样转四次之后统计答案才可以不重不漏。最后会只剩 \((x,y)\) 自身没统计到,答案 \(+1\) 即可。如下图,四次旋转统计部分涵盖了除 \((x,y)\) 自身外的所有点,当前统计黑绿两框的交集。
先扫描线,\(y\) 从左到右扫。考虑统计答案的过程,是 \(x\) 从询问点坐标往上跑一遍,维护一个值 \(k\),碰到左边伸过来的一根柱子,就让 \(k\) 和柱子长度取 \(\max\),并让答案加上 \(y-k\)。"Formally",设 \(S\) 里 \(x\) 坐标为 \(i\) 的点中,\(y\) 坐标的最大值为 \(R_i\),答案为
最后减去 \(x-R_x\) 是因为 \(x\) 这一行不能算,去重需要。现在问题变成了,动态修改 \(R_i\) 并询问:\(R\) 数组以 \(x\) 为右端点的前缀,对其每一个后缀 \(\max\) 求和。
这个线段树有点神奇。区间 \([l,r]\) 维护的是:\(\large \sum_{i=l}^r\max_{j=i}^r R_j\)。那么查询就是把 \([1,x]\) 拆成 \(\log\) 个区间然后依次合并。考虑如何合并 \([l_0,l-1]\) 与 \([l,r]\)。
发现,右边区间 \([l,r]\) 上每一位的贡献不变,令 \(m\) 为右区间的最大值 \(\max_{i=l}^r R_i\),\(p\) 为左边区间 \([l_0,l-1]\) 中最靠右的后缀 \(\max\) \(suf_p\ge m\) 的位置,那么 \([l_0,p]\) 上每一位的贡献也不变。唯一改变的是,\((p,l-1]\) 上的每一位都被推平成了 \(m\)。
区间维护的是 \(suf\) 的和,那区间推平就不难做。考虑如何找到 \(p\)。区间额外维护 \(\max\)(也就是左端点的 \(suf\) 取值),线段树上二分找即可。那么 pushup
时要做一个递归,时间复杂度 \(\Theta(n\log^2 n)\),具体实现见代码。
//为什么这个能过????????????????
//这个咋可能能过???
//我们应该考虑扫行然后算后面的贡献
// 这个就是能过!!!
#include <cstdio>
#include <algorithm>
#include <cstring>
#define ll long long
using namespace std;
const int N=214514;
namespace sgt{
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
struct node{
ll s;int mx;
#define s(x) t[x].s
#define mx(x) t[x].mx
}t[N<<2];
ll fnd(int now,int ln,int rn,int x){
if(ln==rn) return max(mx(now),x);
int mid=ln+rn>>1;
if(mx(rs(now))>=x) return s(now)-s(rs(now))+fnd(rs(now),mid+1,rn,x);
else return fnd(ls(now),ln,mid,x)+1ll*x*(rn-mid);
}
node updata(int x,int lx,int rx,node b){
return (node){b.s+fnd(x,lx,rx,b.mx),max(mx(x),b.mx)};
}
void upd(int now,int ln,int rn,int p,int y){
if(ln==rn) return s(now)=mx(now)=y,void();
int mid=ln+rn>>1;
if(p<=mid) upd(ls(now),ln,mid,p,y);
else upd(rs(now),mid+1,rn,p,y);
t[now]=updata(ls(now),ln,mid,t[rs(now)]);
}
node qry(int now,int ln,int rn,int r){
if(r<ln) return (node){0,0};
if(rn<=r) return t[now];
int mid=ln+rn>>1;
if(r<=mid) return qry(ls(now),ln,mid,r);
else return updata(ls(now),ln,mid,qry(rs(now),mid+1,rn,r));
}
}
int x[N],y[N],n,m,K,Q;
void rotat(){
for(int i=1;i<=K+Q;++i){
int a=m-y[i]+1,b=x[i];
x[i]=a;y[i]=b;
}
swap(n,m);
}
int p[N],q[N],cur[N];ll ans[N];
void fuc(){
memset(sgt::t,0,sizeof(sgt::t));memset(cur,0,sizeof(cur));
sort(p+1,p+K+1,[](int a,int b){return y[a]<y[b];});
sort(q+1,q+Q+1,[](int a,int b){return y[a]<y[b];});
for(int i=1,a=0,b=0;i<=m;++i){
while(a<K&&y[p[a+1]]==i){++a;
sgt::upd(1,1,n,x[p[a]],i);cur[x[p[a]]]=i;
}
while(b<Q&&y[q[b+1]]==i){++b;
ans[q[b]-K]+=1ll*i*x[q[b]]-sgt::qry(1,1,n,x[q[b]]).s-i+cur[x[q[b]]];
}
}
}
int main()
{
scanf("%d%d%d%d",&n,&m,&K,&Q);
for(int i=1;i<=K;++i) p[i]=i,scanf("%d%d",x+i,y+i);
for(int i=K+1;i<=K+Q;++i) q[i-K]=i,scanf("%d%d",x+i,y+i);
int t=4;while(t--) fuc(),rotat();
for(int i=1;i<=Q;++i) printf("%lld\n",ans[i]+1);
}