题解:洛谷 P1106 删数问题
【题目来源】
洛谷:P1106 删数问题 - 洛谷(luogu.com.cn)
【题目描述】
键盘输入一个高精度的正整数 \(N\)(不超过 \(250\) 位),去掉其中任意 \(k\) 个数字后剩下的数字按原左右次序将组成一个新的非负整数。编程对给定的 \(N\) 和 \(k\),寻找一种方案使得剩下的数字组成的新数最小。
【输入】
输入两行正整数。
第一行输入一个高精度的正整数 \(n\)。
第二行输入一个正整数 \(k\),表示需要删除的数字个数。
【输出】
输出一个整数,最后剩下的最小数。
【输入样例】
175438
4
【输出样例】
13
【核心思想】
-
问题分析:给定高精度正整数 \(N\)(最多250位),删除其中 \(k\) 个数字,使剩余数字组成的新数最小。这是一个贪心单调栈问题,关键在于从左到右遍历,维护一个递增的单调栈,遇到更小的数字时弹出栈顶较大的数字(相当于删除),保证高位尽可能小。
-
算法选择:
- 贪心策略:在可删除的范围内选择最小的数字作为当前位。具体地,第 \(i\) 个保留的数字应在 \([mark, k+i]\) 范围内选择最小值(保证后面还有足够的数字可供删除和填充)
- 范围查找:
find_min(l, r)在 \([l, r]\) 范围内查找最小数字,同时更新下次查找的起始位置
-
关键步骤:
- 读入数据:字符串 \(s\)(数字序列)和整数 \(k\)(删除个数),\(n = s.length()\)
- 构建结果(保留 \(n-k\) 位,\(i\) 从 \(0\) 到 \(n-k-1\)):
tempi = find_min(mark, k + i):在可删除范围内找最小值- 范围右边界 \(r = k + i\):确保后面还有 \(n - 1 - (k + i) = n - k - i - 1\) 个数字,加上当前选的1个,共 \(n - k - i\) 个,正好填满剩余位数
- 前导零处理:若
tempi == 0 && flag == 0,跳过不输出 flag = 1,输出tempi
- 全零处理:若
flag == 0,输出 \(0\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n \cdot k)\) 或 \(O(n^2)\),每次
find_min线性扫描,共 \(n-k\) 次 - 空间复杂度:\(O(n)\),存储字符串
- 时间复杂度:\(O(n \cdot k)\) 或 \(O(n^2)\),每次
-
贪心范围查找的核心思想:
- 高位优先原则:数字的大小主要由高位决定,因此每一步应在允许范围内选择最小的数字作为当前高位
- 可行范围的约束:第 \(i\) 步(从0开始)时,已选了 \(i\) 个数字,还需选 \(n-k-i\) 个。当前可选范围右边界为 \(k+i\),确保后面至少还有 \(n-k-i-1\) 个数字可供后续选择
- 前导零的跳过:结果的最高位不能为0(除非结果本身就是0),因此遇到前导零时跳过
- 与单调栈的等价性:该算法等价于维护一个单调递增栈,遇到更小的数字时弹出前面较大的数字(删除),只是实现方式不同
- 适用于"删除k位使数字最小"的经典贪心问题,核心在于高位优先和范围约束
【解题思路】


【算法标签】
普及 #贪心
【代码详解】
#include <bits/stdc++.h> // 包含标准库头文件(万能头文件)
using namespace std; // 使用标准命名空间
string s; // 定义字符串:存储输入的数字序列
int n, k, tempi; // 定义变量:数字长度n,删除数字数k,临时存储最小值
int mark = 0, flag = 0; // 定义变量:查找起始位置mark,前导零标记flag
/**
* 在指定范围内查找最小数字
* @param l 查找范围的左边界
* @param r 查找范围的右边界
* @return 找到的最小数字
*/
int find_min(int l, int r)
{
int minc = s[l]; // 初始化最小值为左边界字符
mark = l + 1; // 更新查找起始位置
// 遍历查找范围内的字符
for (int i = mark; i <= r; i++)
{
if (s[i] < minc) // 找到更小的数字
{
minc = s[i]; // 更新最小值
mark = i + 1; // 更新查找起始位置
}
}
return minc - '0'; // 返回数字值(字符转整数)
}
int main()
{
cin >> s; // 输入数字序列
n = s.length(); // 获取数字长度
cin >> k; // 输入要删除的数字个数
// 构建最小数字序列(保留n-k位)
for (int i = 0; i < n - k; i++)
{
tempi = find_min(mark, k + i); // 查找当前范围内的最小值
// 处理前导零情况
if (tempi == 0 && flag == 0)
{
continue;
}
flag = 1; // 标记已出现非零数字
cout << tempi; // 输出当前数字
}
// 处理全零情况
if (flag == 0)
cout << 0;
return 0; // 程序正常结束
}
【运行结果】
175438
4
13
浙公网安备 33010602011771号