【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
*/
【hdu 6089】Rikka with Terrorist的更多相关文章
- 【hdu 5632】Rikka with Array
Description As we know, Rikka is poor at math. Yuta is worrying about this situation, so he gives Ri ...
- 【数位dp】【HDU 3555】【HDU 2089】数位DP入门题
[HDU 3555]原题直通车: 代码: // 31MS 900K 909 B G++ #include<iostream> #include<cstdio> #includ ...
- 【HDU 5647】DZY Loves Connecting(树DP)
pid=5647">[HDU 5647]DZY Loves Connecting(树DP) DZY Loves Connecting Time Limit: 4000/2000 MS ...
- -【线性基】【BZOJ 2460】【BZOJ 2115】【HDU 3949】
[把三道我做过的线性基题目放在一起总结一下,代码都挺简单,主要就是贪心思想和异或的高斯消元] [然后把网上的讲解归纳一下] 1.线性基: 若干数的线性基是一组数a1,a2,a3...an,其中ax的最 ...
- 【HDU 2196】 Computer(树的直径)
[HDU 2196] Computer(树的直径) 题链http://acm.hdu.edu.cn/showproblem.php?pid=2196 这题可以用树形DP解决,自然也可以用最直观的方法解 ...
- 【HDU 2196】 Computer (树形DP)
[HDU 2196] Computer 题链http://acm.hdu.edu.cn/showproblem.php?pid=2196 刘汝佳<算法竞赛入门经典>P282页留下了这个问题 ...
- 【HDU 5145】 NPY and girls(组合+莫队)
pid=5145">[HDU 5145] NPY and girls(组合+莫队) NPY and girls Time Limit: 8000/4000 MS (Java/Other ...
- 【hdu 1043】Eight
[题目链接]:http://acm.hdu.edu.cn/showproblem.php?pid=1043 [题意] 会给你很多组数据; 让你输出这组数据到目标状态的具体步骤; [题解] 从12345 ...
- 【HDU 3068】 最长回文
[题目链接] http://acm.hdu.edu.cn/showproblem.php?pid=3068 [算法] Manacher算法求最长回文子串 [代码] #include<bits/s ...
随机推荐
- C基础知识(10):预处理器
C预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤.简言之,C预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理. 所有的预处理器命令都是以井号(#)开 ...
- java:nginx(java代码操作ftp服务器)
1.检查是否安装了vsftpd [root@linux01 ~]# rpm -qa|grep vsftpd 2.安装vsftpd [root@linux01 ~]# yum -y install vs ...
- Dapper 多表(三表以上)查询小技巧
在使用Dappr做查询的时候遇到多表查询,之前多是两张表,现在出现三张表或者更多.两表的时候使用splitOn进行分割,splitOn的默认值是Id.在我建库的时候,主键ID并不都是这个名字.当出现三 ...
- PJzhang:今天才搞清身份证、银行卡……的编码规则
猫宁!!! 之前思考过常见证件的编码规则,抽空查了一下,发现挺有意思. 一般查询证件或者手机号归属地都是直接百度小工具,但是背后的查询机制如何,可能大多人不甚了解. 介绍几种生活中最 ...
- xgboost的使用
1.首先导入包 import xgboost as xgb 2.使用以下的函数实现交叉验证训练xgboost. bst_cvl = xgb.cv(xgb_params, dtrain, num_boo ...
- __str__,__repr__,__add__
class School: def __init__(self,name,addr,type): self.name=name self.addr=addr self.type=type def __ ...
- elasticsearch的cross_fields查询
1.most_fields 这种方式搜索也存在某些问题 它不能使用 operator 或 minimum_should_match 参数来降低次相关结果造成的长尾效应. 2.词 peter 和 smi ...
- XML 基本概念和XPath选择
books.xml文件 <?xml version="1.0" encoding="ISO-8859-1"?> <bookstore> ...
- java 实现读取某个目录下指定类型的文件
我这里是读取txt类型的文件,在指定的目录下有不同类型的文件 实现代码,读取txt类型的文件并打印出该文件的绝对路径 package com.SBgong.test; import java.io.F ...
- __VA_ARGS__用法
转载 自定义调试信息的输出 调试信息的输出方法有很多种, 例如直接用printf, 或者出错时使用perror, fprintf等将信息直接打印到终端上, 在Qt上面一般使用qDebug,而守护进 ...