ACM/ICPC 之 快排+归并排序-记录顺序对(TSH OJ-LightHouse(灯塔))

时间:2021-03-16 08:40:15

TsingHua OJ 上不能使用<algorithm>头文件,因此需要手写快排(刚开始写的时候自己就出了很多问题....),另外本题需要在给横坐标排序后,需要记录纵坐标的顺序对的数量,因此,最快的算法貌似只有归并排序或者树状数组的方法进行顺序对的查找和记录了,时间度为O(nlogn),另外此前需要一次对横坐标的排序,这里用快排。

灯塔(LightHouse)


描述

海上有许多灯塔,为过路船只照明。

ACM/ICPC 之 快排+归并排序-记录顺序对(TSH OJ-LightHouse(灯塔))

如图一所示,每个灯塔都配有一盏探照灯,照亮其东北、西南两个对顶的直角区域。探照灯的功率之大,足以覆盖任何距离。灯塔本身是如此之小,可以假定它们不会彼此遮挡。

若灯塔A、B均在对方的照亮范围内,则称它们能够照亮彼此。比如在图二的实例中,蓝、红灯塔可照亮彼此,蓝、绿灯塔则不是,红、绿灯塔也不是。

现在,对于任何一组给定的灯塔,请计算出其中有多少对灯塔能够照亮彼此。

ACM/ICPC 之 快排+归并排序-记录顺序对(TSH OJ-LightHouse(灯塔))

输入

共n+1行。

第1行为1个整数n,表示灯塔的总数。

第2到n+1行每行包含2个整数x, y,分别表示各灯塔的横、纵坐标。

输出

1个整数,表示可照亮彼此的灯塔对的数量。

Example

Input

3
2 2
4 3
5 1

Output

1

限制

对于90%的测例:1 ≤ n ≤ 3×105

对于95%的测例:1 ≤ n ≤ 106

全部测例:1 ≤ n ≤ 4×106

灯塔的坐标x, y是整数,且不同灯塔的x, y坐标均互异

1 ≤ x, y ≤ 10^8

时间:2 sec

内存:256 MB

提示

注意机器中整型变量的范围,C/C++中的int类型通常被编译成32位整数,其范围为[-231, 231 - 1],不一定足够容纳本题的输出。


解题思路:

  乱序的坐标对我们解题是没有帮助的,因此我们首先应该想到对横坐标(纵坐标)做一次排序,然后考虑纵坐标(横坐标)。

在这里我先对横坐标进行排序,然后从小到大对纵坐标的要求进行归纳,我们可以发现:如果存在两个灯塔A(x1,y1),B(x2,y2),那么x1<x2时,当且仅当y1<y2时,A B两灯塔才能相互beacon(照亮),因此,这道题可以转化为当横坐标顺序确定时,去记录纵坐标的顺序对数量。

  例如A(1,2),B(2,4),C(3,5),那么显然ABC中任两灯塔间可以相互beacon,其对数就是三对。

  ——我们用顺序对来描述就是:A B C顺序摆放,其中AB,AC,BC的纵坐标(<2,4>,<2,5>,<4,5>)各为一个顺序对,因此有三对灯塔可以相互beacon,这与我们的分析是一致的。

时间度分析:

  那么在分析完这道题目后,我们就需要做两件事情,第一件事就是对横坐标进行一次排序,第二件事就是利用某种算法计算出横坐标排序后,纵坐标的顺序对的数量。

  在题目中给出 全部测例:1 ≤ n ≤ 4×10这个数据量是很大的,因此我们必须要用快排对横坐标排序,时间度认为是O(nlogn),第二件事中,联系到逆序对的记录,我们可以用到的最快算法有归并排序和树状数组,这里我们试用归并排序进行顺序对记录,时间度也认为是O(nlogn)。

  以下是实现Code:

 #include<stdio.h>

 #define MAX 4000005
long ans; struct Light{
int x, y;
}l[MAX]; int tmp[MAX]; /*快排*/
void quickSort(int low, int high)
{
int i = low;
int j = high;
Light x = l[low]; //设置一个基准点
do{
while (l[i].x < x.x) i++; //Let l[i].x >= x
while (l[j].x > x.x) j--; //Let l[j].x <= x
if(i <= j){ //SWAP
Light t = l[i];
l[i] = l[j];
l[j] = t;
i++; j--;
}
} while (i <= j); //使得p两侧满足 左<=p,右>=p
if(i < high) quickSort(i, high);
if(j > low ) quickSort(low, j);
} void merge(int low, int mid, int high)
{
int s = low, t = mid + , k = low;
while (s <= mid && t <= high){
if (l[s].y < l[t].y){
ans += high - t + ; //顺序对-右侧未放入的date数量
tmp[k++] = l[s++].y;
}
else tmp[k++] = l[t++].y;
}
if (s == mid + ) while (k <= high) tmp[k++] = l[t++].y;
else while (k <= high) tmp[k++] = l[s++].y;
//COPY
for (int i = low; i <= high; i++) l[i].y = tmp[i];
} /*归并排序*/
void mergeSort(int low, int high)
{
if (low < high){
int mid = (low + high) / ;
mergeSort(low, mid);
mergeSort(mid + , high);
merge(low, mid, high);
}
} int main()
{
int n;
scanf("%d", &n);
for (int i = ; i < n; i++)
scanf("%d%d", &l[i].x, &l[i].y);
quickSort(, n - );//横坐标快排
mergeSort(, n - );//纵坐标归并排序并记录顺序对 printf("%ld\n", ans); return ;
}

小墨- -原创

  尽管如此,但是我们依然只能A掉95%的样例,说明我们的算法依然不够快,依然要进行优化,那么在这里效果最显著的方法

  一个就是改归并排序树状数组可能相对要更快一些,另一个就是优化手写的快速排序算法,可以采用生成随机数或者采用三数取中等等优化方法使得我们手写的快排更趋近稳定,但是由于时间原因,小编没有尝试下去。