清华OJ:PA1-3 灯塔(LightHouse)难题详解与卡点分析(真正解法)

题目:https://dsa.cs.tsinghua.edu.cn/oj/problem.shtml?id=1144

分析前言:

网上有很多解决此题的代码,但几乎无一例外都是用快速排序+归并排序解决,但我认为这是“敷衍”的做法,为什么?因为根据此题所对应的课程进度,排序算法目前只涉及了归并排序,所以只使用归并排序完全可以解决,不信看如下解析。

 

分析:

通过给出几个具体样例,观察,不难发现,若将各点按x的大小非降序排列,则在各点的y坐标中存在逆序对的话,那么此二y坐标所在点即灯塔不能相互照亮。逆序对是指在一维数组a中,下标i<j,若a[i]>a[j],则a[i],a[j]构成逆序对。题目要求能相互照亮的灯塔对数,也就是求顺序对数。求顺序对数,暴力枚举显然会超时,所以一般用分治的思想(Divide and Conquer)求解,即用归并排序(MergeSort)递归式地求解。在每一次二路归并时,当前两数组已经有序,假设其中一数组为b,下标为j,长度为lb,另一数组为c,下标为k,长度为lc,则当j和k都未越界且b[j]<c[k]时,c[k]与b[j]构成顺序对,而c有序,也即非降序,故k之后的元素皆不小于c[k],故与b[j]构成顺序对的个数为1+k之后的元素个数,若j,k均从0开始,则顺序对个数为lc-k。求顺序对个数,是要在每一次二路归并的时候累加,故可设为全局变量供各个递归实例使用。在确定了大致算法后,需要进一步设计出高效的算法策略,根据题目,涉及的数据类型一般是结构体和long long,结构体内存放long long类型的坐标x和y。首先依据各个结构体的x值对结构体数组进行归并排序,这步是必须的。接下来应该避免对结构体数组排序,因为根据本题,结构体数组排序的每次赋值操作次数是普通数组的两倍,容易导致超时,所以先用一重循环,使用long long类型的数组保留所有结构体的y值,再对此数组排序并记录顺序对数。

 

卡点分析:

以下是使用邓公的MergeSort版本解决此题可能遇到的问题:

1.为什么我使用邓公的MergeSort算法,有很多测试点显示结果错误?

这应该是Merge函数中的b数组,也就是每次二路归并时使用的临时数组在开头定义但没有用动态分配的方式所致,题目中需要用到两个Merge函数,一个对结构体数组排序,另一个对long long数组排序,但无论什么类型的数组,若作为临时数组并在开头定义的话,都必须使用动态分配的方式,在C++中是用new,否则将导致错误。

 

2.为什么我使用了邓公的MergeSort算法而且临时数组也在开头用new分配了,结果还是很多错误?

这个是本题最大的坑点,那就是记录顺序对数的变量必须是long long 类型!

 

3.为什么我使用邓公的MergeSort算法,最后一个case还是过不了?

这应该是你使用了邓公的MergeSort普通版本,如果你此课程学得认真的话,你会注意到视频中还讲了一种MergeSort的优化版本,很多人都把它忽略了,但是放到此题上,它的作用就体现出来了,关于此优化版本,我会在之后给出说明。

 

4.我不知道什么MergeSort优化版本,我就用邓公一开始讲的MergeSort,能通过吗?

能,但是你需要在输入前加上如下输入加速语句:

setvbuf(stdin, new char[1<<20], _IOFBF,1<<20);

 

下面给出MergeSort的两个版本的核心语句,其实优化版本只优化了核心语句

MergeSort普通版本核心语句:

for(int i=0,j=0,k=0;j<lb||k<lc;)
{
	if(j<lb&&(lc<=k||b[j]<=c[k]))a[i++]=b[j++];
	if(k<lc&&(lb<=j||c[k]<b[j]))a[i++]=c[k++];
}

MergeSort优化版本核心语句:

/*
由于c是a中的元素,故当j越界时无需转移c中的元素至a,
只要b数组比较完,即j越界,就结束循环,故删除原来判定j的语句,
*/
for(int i=0,j=0,k=0;j<lb;)
{
	if(lc<=k||b[j]<=c[k])a[i++]=b[j++];
	if(k<lc&&c[k]<b[j])a[i++]=c[k++];
}

完整AC代码(此题的真正解法):

#include<cstdio>
#define MAXSIZE 4000000
using namespace std;
typedef long long ll;
struct Point{
	ll x,y;
}points[MAXSIZE];
Point *bs=new Point[MAXSIZE];
ll *b=new ll[MAXSIZE];//定义归并排序要用到的临时数组,注意必须都用new分配 
ll y[MAXSIZE],count=0;//定义保留y值的数组,注意count必须为ll类型 
void MergeStruct(Point *elem,int lo,int mi,int hi)
{//根据x值对结构体数组排序 
	Point *a=elem+lo,*c=elem+mi;
	int lb=mi-lo,lc=hi-mi;
	for(int i=0;i<lb;bs[i]=a[i++]);
	for(int i=0,j=0,k=0;j<lb;)
	{
		if(lc<=k||bs[j].x<c[k].x)a[i++]=bs[j++];
		if(k<lc&&c[k].x<=bs[j].x)a[i++]=c[k++];
	}
}
void Mergell(ll *elem,int lo,int mi,int hi)
{//对保存了y值的ll类型的数组排序 
	ll *a=elem+lo,*c=elem+mi;
	int lb=mi-lo,lc=hi-mi;
	for(int i=0;i<lb;b[i]=a[i++]);
	for(int i=0,j=0,k=0;j<lb;)
	{
		if(lc<=k||b[j]<c[k]){
			a[i++]=b[j++];//若 b[j]<c[k],而c有序,k之后的元素都大于b[j] 
			if(k<lc)count+=lc-k;//只要k不越界,那么k之后的元素个数+1就是顺序对数
		}
		if(k<lc&&c[k]<=b[j])a[i++]=c[k++];
	}
}
void MergeSort_X(Point *elem,int lo,int hi)
{
	if(hi-lo<2)return;
	int mi=(hi+lo)>>1;
	MergeSort_X(elem,lo,mi);
	MergeSort_X(elem,mi,hi);
	MergeStruct(elem,lo,mi,hi);
}

void MergeSort_Y(ll *elem,int lo,int hi)
{
	if(hi-lo<2)return;
	int mi=(hi+lo)>>1;
	MergeSort_Y(elem,lo,mi);
	MergeSort_Y(elem,mi,hi);
	Mergell(elem,lo,mi,hi);
}
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	scanf("%lld %lld",&points[i].x,&points[i].y);//输入,注意lld 
	MergeSort_X(points,0,n);
	for(int i=0;i<n;i++)
	y[i]=points[i].y;//保留y值 
	MergeSort_Y(y,0,n);
	printf("%lld\n",count);//输出,注意lld 
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_37729102/article/details/82756328
PA1
pa