最长上升子序列的优化求法和Dilworth定理
最长上升子序列
学过DP的都知道,求最长上升子序列的DP做法的时间复杂度是\(O(n^2)\)的,现在介绍一个\(O(n*\log n)\)的二分做法
二分做法
3 5 2 3 7 1一组原始数据,最长上升子序列的长度应该是3,为序列2 3 7
使用队列q,先把第一个元素放进去
q: 3
从第二个元素 x 开始只要满足最长上升子序列的条件就加在后面,不满足就在队列中找第一个比 x 大的元素替换 (用二分查找)
q: 3 5
q: 2 5
q: 2 3
q: 2 3 7
q: 1 3 7 这是最后一个状态,此时队列的长度即是最长上升子序列的长度,但是内容不是正确的那个最长上升子序列
- 此做法只关注最后的长度,不关注内容是否正确
Dilworth定理
原链的最长长度=反链划分数的最小值
这是简单的总结,原始定义网上有很多
原链和反链?
不上升子序列 和 上升子序列互为反链
不下降子序列 和 下降子序列互为反链
链的划分数?
假设链是上升子序列,则按照上升子序列的规则对链进行划分,得到的满足规则的小链的数量即是链的划分数
重要例题
P1020 [NOIP1999 提高组] 导弹拦截
思路
第一问:根据题意很容易想到,就是一道求最长非上升子序列的问题,也很容易想到DP的做法
但是这道题会卡\(O(n^2)\)的做法,所以要采用二分法\(O(n * \log n)\)
第二问:第二问其实就是求不上升子序列的最小划分数,根据Dilworth定理,可以转化成求最长上升子序列的长度
代码
#include <bits/stdc++.h>
typedef std::pair<int, int> pii;
#define INF 0x3f3f3f3f
#define MOD 998244353
using i64 = long long;
const int N = 1e5+5;
int a[N];
void solve(){
int n = 0;
while (std::cin >> a[n]){
n++;
}
std::vector<int> lnis, lis;
lnis.push_back(a[0]);
lis.push_back(a[0]);
for (int i = 1; i < n; i++){
if (*lnis.rbegin() >= a[i]){
lnis.push_back(a[i]);
}else{
int k = std::upper_bound(lnis.begin(), lnis.end(), a[i], std::greater<int>()) - lnis.begin();
lnis[k] = a[i];
}
if (*lis.rbegin() < a[i]){
lis.push_back(a[i]);
}else{
int k = std::lower_bound(lis.begin(), lis.end(), a[i]) - lis.begin();
lis[k] = a[i];
}
}
std::cout << lnis.size() << '\n' << lis.size();
}
signed main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout<<std::setiosflags(std::ios::fixed)<<std::setprecision(2);
int t = 1, i;
for (i = 0; i < t; i++){
solve();
}
return 0;
}
标签:std,定理,求法,lnis,lis,序列,Dilworth,上升,最长
From: https://www.cnblogs.com/califeee/p/18648524