P1020 导弹拦截
题目大意
求一个数列的最长不上升子序列的长度和最优经过几次最长不上升子序列的覆盖可以覆盖完整个数列。
思路分析
$O(n^2)$ 做法这里不提供。仅介绍 $O(n\log n)$ 做法。
整体思想:贪心+二分
最多能拦截多少很简单。我们用数组维护一个栈。这个栈具有单调性。
然后将原数组从头到尾扫一遍。每次扫到一个数,我们对其进行以下操作:
-
如果这个数 $\le$ 栈顶:
将这个数直接入栈。
-
否则:
找到栈里最小的大于该数的元素,将这个元素替换成这个数。因为栈的单调性,使用二分查找即可。
这样,一共扫 $n$ 次,每次的最坏时间复杂度 $O(\log n)$,总时间复杂度 $O(n\log n)$。
那么,如何求要覆盖多少次呢?
先来看一个定理:
Dilworth定理
对于一个偏序集,最少链划分等于最长反链长度。是不是很懵?在这道题中,我可以用人话复述一遍:
最长上升子序列的长度就是能构成的不上升序列的个数。
这样,我们经过简易的推理,就可以得出:
要使用导弹的次数就是最长上升子序列的长度。
如何求最长上升子序列呢?我们把求最长不上升子序列的代码稍微改动一下就好了。
这样,我们就可以在 $O(n\log n)$ 的时间复杂度内通过本题。
完整代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 1;
int n;
int a[maxn];
int cnt;
int s[maxn];
void input() {
do {
n++;
} while (cin >> a[n]);
n--;
}
void flush() {
memset(s, 0, sizeof(s));
cnt = 1;
}
int lg_uni_seq() {
flush();
s[1] = a[1];
for (int i = 2; i <= n; i++) {
if (a[i] <= s[cnt]) {
s[++cnt] = a[i];
} else {
int l = 1, r = cnt;
while (l < r) {
int mid = (l + r) >> 1;
if (a[i] > s[mid]) {
r = mid;
} else {
l = mid + 1;
}
}
s[l] = a[i];
}
}
return cnt;
}
int lg_inc_seq() {
flush();
s[1] = a[1];
for (int i = 2; i <= n; i++) {
if (a[i] > s[cnt]) {
s[++cnt] = a[i];
} else {
int l = 1, r = cnt;
while (l < r) {
int mid = (l + r) >> 1;
if (a[i] <= s[mid]) {
r = mid;
} else {
l = mid + 1;
}
}
s[l] = a[i];
}
}
return cnt;
}
int main() {
input();
cout << lg_uni_seq() << endl << lg_inc_seq() << endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现