洛谷P1923 【深基9.例4】求第k小的数

文章介绍了一种优化的快速排序方法,用于寻找输入列表中第k大的数字,涉及分治策略和时间复杂度优化。作者在实践中遇到超时问题,通过更换输入方式解决。

题目描述

输入n(1 \leq n \leq 5000000且n为奇数)个数字a_i(1\leq a_i \leq 10^9),输出这些数字的第k小的数。最小的数是第0小。

请尽量不要使用nth_element来写本题,因为本题的重点在于练习分治算法。

输入格式

输出格式

输入输出样例

输入

5 1

4 3 2 1 5

输出

2

解题过程

我们首先解读一下题目的要求,即第一行输入两个数n和k,第二行输入n个数,输出n个数中第k大的数。

由于n的数量级不小,我们直接使用long类型存储,同样,a也使用long类型,而关于分治算法,我在此处使用的是快速排序。

然而,直接使用快速排序,仍然无法通过这道题,能够获得分数为60。

以下为普通快速排序的函数:

//排序函数
long Partition(long first, long last)
{
	long i = first, j = last, temp = 0;
	while (i < j)
	{
		//右侧扫描
		while (i < j && arr[i] <= arr[j])
			j--;
		if (i < j) {
			temp = arr[i];
			arr[i] = arr[j];
			arr[j] = temp;
			i++;
		}
		//左侧扫描
		while (i < j && arr[i] <= arr[j])
			i++;
		if (i < j) {
			temp = arr[i];
			arr[i] = arr[j];
			arr[j] = temp;
			j--;
		}
	}
	return i;
}

//递归排序
void QuickSort(long first, long last)
{
	if (first >= last)
		return;
	else {
		long pivot = Partition(first, last);
		QuickSort(first, pivot - 1);
		QuickSort(pivot + 1, last);
	}
}

对于这一情况,我们能够做的就是优化代码,由于快速排序是对所选定的轴值的两侧分别排序,我们可以通过判定语句,仅对包含位置为k的一侧进行排序,这样我们可以减少很多的运行时间。

优化后的代码如下:

void QuickSort(long first, long last, long k)
{
	if (first >= last)
		return;
	else 
	{
		long pivot = Partition(first, last);
		if(pivot > k)
			QuickSort(first, pivot - 1, k);
		else if(pivot < k)
			QuickSort(pivot + 1, last, k);
		else
		{
            //输出后直接终止程序,防止重复打印
			cout << arr[pivot];
			exit(0);
		}
	}
}

然而,仅优化快速排序的算法还是没有办法通过这道题,甚至通过的测试点仍然没有变多,依旧超时了。

在那个时候我可以说是百思不得其解,算法已经优化了,怎么还是过不了,看题解后仍旧没有找到解决的方法。所以我直接润去百度,找有没有优化运算时间的方法。

"cin",这个我一直在用的东西,没有想到竟然是它的锅。检索后发现最有可能造成超时的是它后,我就改换了scanf来进行输入。嗯,不超时了,成功AC。

AC代码

#define _CRT_SECURE_NO_WARNINGS //visual studio 2022使用scanf需添加
#include<iostream>
#include<stdio.h>
using namespace std;

long Partition(long first, long last);
void QuickSort(long first, long last , long k);

long arr[5000005] = { 0 };

int main()
{
	//输入n和k
	long n = 0;
	long k = 0;
	scanf("%ld%ld", &n,&k);
	//输入各个数
	for (long i = 0; i < n; i++)
		scanf("%ld",&arr[i]);
	//到k的快速排序
	QuickSort(0, n - 1, k);
	//输出第k大的值
	cout << arr[k];
	return 0;
}

long Partition(long first, long last)
{
	long i = first, j = last, temp = 0;
	while (i < j)
	{
		//右侧扫描
		while (i < j && arr[i] <= arr[j])
			j--;
		if (i < j) {
			temp = arr[i];
			arr[i] = arr[j];
			arr[j] = temp;
			i++;
		}
		//左侧扫描
		while (i < j && arr[i] <= arr[j])
			i++;
		if (i < j) {
			temp = arr[i];
			arr[i] = arr[j];
			arr[j] = temp;
			j--;
		}
	}
	return i;
}

void QuickSort(long first, long last, long k)
{
	if (first >= last)
		return;
	else 
	{
		long pivot = Partition(first, last);
		if(pivot > k)
			QuickSort(first, pivot - 1, k);
		else if(pivot < k)
			QuickSort(pivot + 1, last, k);
		else
		{
			cout << arr[pivot];
			exit(0);
		}
	}
}

### 题目解析 P1151 子问题,通常是给定一个整 \(K\),然后在一个指定的区间 \([L, R]\) 内寻找满足特定条件的整 \(N\)。条件一般是 \(N\) 存在连续的子能被 \(K\) 整除。如,对于整 \(N = 1234\),它的子有 \(1\)、\(2\)、\(3\)、\(4\)、\(12\)、\(23\)、\(34\)、\(123\)、\(234\)、\(1234\) 等,如果其中有某个子能被 \(K\) 整除,那么 \(N\) 就是符合要。 ### 解题思路 1. 遍历区间 \([L, R]\) 内的每一个整 \(N\)。 2. 对于每个整 \(N\),将其转换为字符串,方便提取所有可能的子。 3. 提取 \(N\) 的所有连续子,并将子转换回整4. 检查这些子中是否有能被 \(K\) 整除的,如果有,则输出该整 \(N\)。 ### 代码实现 ```cpp #include <iostream> #include <string> using namespace std; // 检查一个是否存在子能被 k 整除 bool hasDivisibleSubnumber(int num, int k) { string str = to_string(num); int len = str.length(); for (int i = 0; i < len; i++) { for (int j = i; j < len; j++) { string subStr = str.substr(i, j - i + 1); int subNum = stoi(subStr); if (subNum % k == 0) { return true; } } } return false; } int main() { int k, l, r; cin >> k >> l >> r; bool found = false; for (int i = l; i <= r; i++) { if (hasDivisibleSubnumber(i, k)) { cout << i << endl; found = true; } } if (!found) { cout << "No" << endl; } return 0; } ``` ### 代码解释 - `hasDivisibleSubnumber` 函用于检查一个是否存在子能被 \(k\) 整除。它将整转换为字符串,然后使用两层循环提取所有可能的连续子,将子转换回整并检查是否能被 \(k\) 整除。 - `main` 函中,首先读取 \(k\)、\(L\) 和 \(R\),然后遍历区间 \([L, R]\) 内的每一个整,调用 `hasDivisibleSubnumber` 函进行检查,如果存在符合条件的子,则输出该整。如果遍历完整个区间都没有找到符合条件的,则输出 "No"。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值