园丁的烦恼|P2163

https://www.luogu.com.cn/problem/P2163

题目背景

很久很久以前,在遥远的大陆上有一个美丽的国家。统治着这个美丽国家的国王是一个园艺爱好者,在他的皇家花园里种植着各种奇花异草。

有一天国王漫步在花园里,若有所思,他问一个园丁道: “最近我在思索一个问题,如果我们把花坛摆成六个六角形,那么……”

“那么本质上它是一个深度优先搜索,陛下。”园丁深深地向国王鞠了一躬。

“嗯……我听说有一种怪物叫九头蛇,它非常贪吃苹果树……”

“是的,显然这是一道经典的动态规划题,早在 N 元 400240024002 年我们就已经发现了其中的奥秘了,陛下。”

“该死的,你究竟是什么来头?”

“陛下息怒,干我们的这行经常莫名其妙地被问到和 OI 有关的题目,我也是为了预防万一啊!” 王者的尊严受到了伤害,这是不可容忍的。

题目描述

看来一般的难题是难不倒这位园丁的,国王最后打算用车轮战来消耗他的实力: “年轻人,在我的花园里有 n 棵树,每一棵树可以用一个整数坐标来表示,一会儿,我的 m 个骑士们会来轮番询问你某一个矩阵内有多少树,如果你不能立即答对,你就准备走人吧!”说完,国王气呼呼地先走了。

这下轮到园丁傻眼了,他没有准备过这样的问题。所幸的是,作为“全国园丁保护联盟”的会长——你,可以成为他的最后一根救命稻草。

输入格式

第一行有两个整数 n,m,分别表示树木个数和询问次数。

接下来 n 行,每行两个整数 x,y,表示存在一棵坐标为 (x,y)的树。有可能存在两棵树位于同一坐标。

接下来 m 行,每行四个整数 a,b,c,d,表示查询以 (a,b)为左下角,(c,d)为右上角的矩形内部(包括边界)有多少棵树。

输出格式

对于每个查询,输出一行一个整数表示答案。

输入输出样例

输入 #1

3 1
0 0 
0 1
1 0
0 0 1 1

输出 #1

3

说明/提示

数据规模与约定

  • 对于30\%的数据,保证 n,m≤10。
  • 对于100\%的数据,保证 0≤n≤500000,1≤m≤500000,0≤x,y,a,b,c,d≤10000000,a≤c,b≤d。

两个不可行的思路:

  • 对于每组询问,检查每棵树符不符合要求(在不在矩阵内)

    复杂度:O(nm),炸了2500倍

  • 二位前缀和,预处理后O(1)查询

    这个更离谱,复杂度:O(10000000*100000000),炸了1000000倍

以上两种复杂度统称O(TLE)//都T了还算个毛线复杂度!

于是,为了解决这类问题,我们采用神奇的方法:一列一列算

(你知道没有图片多 难 。 受 )

因为观察数据发现有很多空行/空列,要是枚举或者预处理会很浪费

1 0 0 0 0 0 0 0 0
0 0 0 1 0 0 0 0 1 
0 0 0 1 0 1 0 0 0 
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1 0
0 0 1 0 0 0 0 0 0

举个栗子

如果我们要在一个一维数组里面快速求区间和,前缀和是个很好的选择

然后我们把前缀和前缀起来就是二位前缀和了(每一列的和不是自己本列的和,而是把自己和前面所有列的和加起来)

如果询问是无序的,那么我们去查询的时候的时候要吧二位前缀和的每个书都存下来,空间都炸了,别说时间,

但我们发现前缀和求法是s[i]=s[i-1]+a[i],它不需要前面所有列的信息

于是我们只需要询问排好序,这样就可以把数组滚动起来,空间就解决了

然后我们再看,由于500000个点在100000000000000个位置中显得真的很渺小

每次前缀和只因为几个数就要花O(10000000)大改一番,很不划算,于是我们用树状数组代替前缀和实现修改查询操作,nlogn完全可以!

虽然nlogn很好,但是最好情况我们也仍然有95\%的时间在访问空列

这很好解决,跳着访问,查询时只在非空列修改与查询

(因为空列的话代码没有运行一行要他干啥……)

#include<iostream>
#include<vector>
#include<map>
#include<set>
#include<algorithm>
using namespace std;
//先打快读
inline int read(){
    int s=0,f=1;
    char c=getchar();
    while(c<48 || c>57){if(c=='-')f=-f;c=getchar();}
    while(c>=48 && c<=57)s=s*10+c-48,c=getchar();
    return s*f;
}
//要打一棵线段树
//离散化后问题不大
const int MAXN=5e5+10;
//难蚌
int n,m;
//换成树状数组
int tc[10000001];
#define lb(x) ((x)&(-(x)))
int query(int x){
    if(x==0)return 0;
    int ret=0;
    for(int i=x;i>0;i-=lb(i))ret+=tc[i];
    return ret;
}
void modify(int x,int v){
    if(x==0)return;
    for(int i=x;i<=10000000;i+=lb(i))tc[i]+=v; 
}
set <int> s;
vector<int>v;  
//纵轴就不离散化了,把横轴离散化了
//map?
struct node{
    int l;
    int r;
    int o;
    bool is_plus;
};
map<int,vector<int> > tree;
map<int,vector<node > > problem;
int ans[MAXN];
//kaiyi的烦恼 o/QWQ\o
signed main(){
    n=read(),m=read();
    int x,y;
    for(int i=0;i<n;i++){
        x=read()+1;
        y=read()+1;
        tree[x].push_back(y);
        s.insert(x);
    }
    v.push_back(0);
    for(auto i:s)v.push_back(i);
    int a=0,b=0,c=0,d=0;
    for(int i=0;i<m;i++){
        a=read()+1,b=read()+1,c=read()+1,d=read()+1;
        int pos;
        pos=lower_bound(v.begin(),v.end(),a)-v.begin()-1;
        pos=v[pos];
        problem[pos].push_back({b,d,i,false});
        pos=upper_bound(v.begin(),v.end(),c)-v.begin()-1;
        pos=v[pos];
        problem[pos].push_back({b,d,i,true});
    }
    for(auto i:tree){
        for(auto j:i.second){
            modify(j,1);
        }
        for(auto j:problem[i.first]){
            if(!j.is_plus){
                ans[j.o]-=query(j.r)-query(j.l-1);
            }
            else{
                ans[j.o]+=query(j.r)-query(j.l-1);
            }
        }
    }
    for(int i=0;i<m;i++)cout<<ans[i]<<endl;

    return 0;
}