二维偏序——常见问题解答

2024-08-23 16:08

本文主要是介绍二维偏序——常见问题解答,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、定义

  • 对于每个点i,都可能有另外一些点的x、y坐标均小于等于点i的x、y坐标,这些点的数量即为点i的二维偏序值.
  • 在图1中,点A的二维偏序值为1,B的二维偏序值为2,点C的二维偏序值为0.
  • 图1
  • 在图2中,点A与点B的二维偏序值均为0.
  • 图2

二、具体过程

  • 很多地方都会直接告诉我们:按照第一维排序,再用树状数组处理第二维即可。但是最重要的并不是具体的运行步骤,而是这个方法里真正蕴含的算法设计的思想.
  • 为什么要按照第一维排序:对于每个点,显然只有它前面的点(x坐标小于等于该点)的数量有可能(换句话说,x坐标大于该点的那些点是绝对不可能被计入该点的二维偏序值的)被计入该点的二维偏序值.
  • 当然了,仅仅按照第一维排序是不能解决这一问题的,因为不能保证每个点前面的点的y坐标都小于等于这个点。换句话说,假设点i前面的某个点的y坐标大于点i的y坐标,那就不应当计入点i的二维偏序值.
  • 例如图3,该图中点A、B、C的二维偏序值均为0.
  • 图3

     

  • 为什么要使用树状数组:在二维偏序中,通过对每个点关于x坐标排序,我们得到了一个x轴坐标单调递增的点的序列。接下来要解决的问题,是怎么关于点i获取y坐标小于点i的点的数量。由于只有x坐标小于等于点i的点集需要被考虑(原因前面已经提到过,即只有x坐标小于等于点i的x坐标的点集有可能被计入点i的二维偏序值),
  • 我们可以从开头到末尾遍历已关于x轴排序每个点,每遍历到一个点,就将这个点的y坐标添加到树状数组中。这样,对于点i,只需要在树状数组中查询y坐标小于等于点i的y坐标的点的数量,即可获取该点的二维偏序值。在具体理解中,我们可以理解为树状数组在其中起到的作用类似一个垂直于y轴的"挡板"(如图4)。换句话说,这里使用的树状数组实际上是关于各个点的y坐标值的,这一点类似值域线段树.
  • 图4
  • 类似地,关于x轴的排序也可以理解为垂直于x轴的"挡板"(图5)(只画出了一部分以便于理解)

  • 图5

     

  • 由此可知,该二维偏序算法的正确性是由按照时间顺序(实际是x坐标的升序)不断向树状数组加点(实际只加了y坐标)保证的.

 


 代码如下(未经过严格测试):

#include<cstdio>
#include<iostream>
#include<queue>
using namespace std;
const int MAXN=1000010;
int maxValue,tr[MAXN];
int lowbit(int x){return x&-x;
}
void add(int x,int k){for(int i=x;i<=maxValue;i+=lowbit(i)){tr[i]+=k;}
}
int sum(int l,int r){int ans=0;for(int i=r;i>0;i-=lowbit(i)){ans+=tr[i];}for(int i=l-1;i>0;i-=lowbit(i)){ans-=tr[i];}return ans;
}
struct Point{int a,b;bool operator <(const Point &another)const{return another.a<a;}
};
int pointCnt=0;
int main(){int n;scanf("%d%d",&n,&maxValue);priority_queue<Point> q;for(int i=1;i<=n;i++){int tmpA,tmpB;scanf("%d%d",&tmpA,&tmpB);Point tmp=Point{tmpA,tmpB};q.push(tmp);}while(!q.empty()){Point nowPoint=q.top();q.pop();int nowValue=nowPoint.b;cout<<sum(1,nowValue)<<endl;add(nowValue,1);}return 0;
}

 

这篇关于二维偏序——常见问题解答的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1099854

相关文章

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

SQL 中多表查询的常见连接方式详解

《SQL中多表查询的常见连接方式详解》本文介绍SQL中多表查询的常见连接方式,包括内连接(INNERJOIN)、左连接(LEFTJOIN)、右连接(RIGHTJOIN)、全外连接(FULLOUTER... 目录一、连接类型图表(ASCII 形式)二、前置代码(创建示例表)三、连接方式代码示例1. 内连接(I

Python安装时常见报错以及解决方案

《Python安装时常见报错以及解决方案》:本文主要介绍在安装Python、配置环境变量、使用pip以及运行Python脚本时常见的错误及其解决方案,文中介绍的非常详细,需要的朋友可以参考下... 目录一、安装 python 时常见报错及解决方案(一)安装包下载失败(二)权限不足二、配置环境变量时常见报错及

Go语言利用泛型封装常见的Map操作

《Go语言利用泛型封装常见的Map操作》Go语言在1.18版本中引入了泛型,这是Go语言发展的一个重要里程碑,它极大地增强了语言的表达能力和灵活性,本文将通过泛型实现封装常见的Map操作,感... 目录什么是泛型泛型解决了什么问题Go泛型基于泛型的常见Map操作代码合集总结什么是泛型泛型是一种编程范式,允

C#多线程编程中导致死锁的常见陷阱和避免方法

《C#多线程编程中导致死锁的常见陷阱和避免方法》在C#多线程编程中,死锁(Deadlock)是一种常见的、令人头疼的错误,死锁通常发生在多个线程试图获取多个资源的锁时,导致相互等待对方释放资源,最终形... 目录引言1. 什么是死锁?死锁的典型条件:2. 导致死锁的常见原因2.1 锁的顺序问题错误示例:不同

Python调用另一个py文件并传递参数常见的方法及其应用场景

《Python调用另一个py文件并传递参数常见的方法及其应用场景》:本文主要介绍在Python中调用另一个py文件并传递参数的几种常见方法,包括使用import语句、exec函数、subproce... 目录前言1. 使用import语句1.1 基本用法1.2 导入特定函数1.3 处理文件路径2. 使用ex

Spring常见错误之Web嵌套对象校验失效解决办法

《Spring常见错误之Web嵌套对象校验失效解决办法》:本文主要介绍Spring常见错误之Web嵌套对象校验失效解决的相关资料,通过在Phone对象上添加@Valid注解,问题得以解决,需要的朋... 目录问题复现案例解析问题修正总结  问题复现当开发一个学籍管理系统时,我们会提供了一个 API 接口去

C语言线程池的常见实现方式详解

《C语言线程池的常见实现方式详解》本文介绍了如何使用C语言实现一个基本的线程池,线程池的实现包括工作线程、任务队列、任务调度、线程池的初始化、任务添加、销毁等步骤,感兴趣的朋友跟随小编一起看看吧... 目录1. 线程池的基本结构2. 线程池的实现步骤3. 线程池的核心数据结构4. 线程池的详细实现4.1 初

poj2576(二维背包)

题意:n个人分成两组,两组人数只差小于1 , 并且体重只差最小 对于人数要求恰好装满,对于体重要求尽量多,一开始没做出来,看了下解题,按照自己的感觉写,然后a了 状态转移方程:dp[i][j] = max(dp[i][j],dp[i-1][j-c[k]]+c[k]);其中i表示人数,j表示背包容量,k表示输入的体重的 代码如下: #include<iostream>#include<

hdu2159(二维背包)

这是我的第一道二维背包题,没想到自己一下子就A了,但是代码写的比较乱,下面的代码是我有重新修改的 状态转移:dp[i][j] = max(dp[i][j], dp[i-1][j-c[z]]+v[z]); 其中dp[i][j]表示,打了i个怪物,消耗j的耐力值,所得到的最大经验值 代码如下: #include<iostream>#include<algorithm>#include<