题目大意
给定一个序列,求出最长不升子序列的长度以及原序列最少分为多少个不升的子序列。
解题思路
Dilworth定理
在有穷偏序集中,任何反链最大元素数目等于任何将集合到链的划分中链的最小数目。
偏序集的两个定理
定理一:令(
X
,
≤
X,\leq
X,≤)是一个有限偏序集,并令
r
r
r 是其最大链的大小。则
X
X
X 可以被划分成
r
r
r 个但不能再少的反链。
其对偶定理称为 Dilworth 定理。
定理二:令( X , ≤ X, \leq X,≤)是一个有限偏序集,并令 m m m 是反链的最大的大小。则 X X X 可以被划分成 m m m 个但不能再少的链。
上述两个定理可以简写为:链的最少划分数等于反链的最长长度
例子
给出一个序列,求出最少将这个序列划分为多少个上升的子序列。
结论
根据狄尔沃斯定理,最少的上升子序列的个数等于最长不升子序列的长度。其中的对应关系为:
- 最少的上升子序列( < < <)的个数等于最长不升子序列( ≥ \geq ≥)的长度
- 最少的不升子序列( ≥ \geq ≥)的个数等于最长上升子序列( < < <)的长度
- 最少的下降子序列( > > >)的个数等于最长不降子序列( ≤ \leq ≤)的长度
- 最少的不降子序列( ≤ \leq ≤)的个数等于最长下降子序列( > > >)的长度
暴力
设置一个数组存起来所有上升子序列的最后一个数,从左到右考虑每个数,然后枚举已经保存的上升子序列的结尾,如果存在一个上升子序列的结尾后面可以接上该数,则接上;否则就需要以该数为起始重新开一个上升子序列。这个做法得到的上升子序列一定是最少的。
int a[maxn], b[maxn], f[maxn];
int solve() {
int cnt = 0;
for(int i = 1; i <= n; i++) {
bool flag = 0;
for(int j = 1; j <= cnt; j++) {
if(f[j] <= a[i]) {
f[j] = a[i];
flag = 1;
break;
}
}
if(!flag) f[++cnt] = a[i];
}
return cnt;
}
证明
不难发现上述暴力做法中,保存的上升子序列的最后一个数一定是单调递增的,因为如果不是单调的那么它一定会接在前面的一个序列上。因此在找可以接上的第一个序列时,可以通过二分去找。
这样优化之后的代码就和找最长不升子序列的代码一模一样,严谨的证明如下:
从第 i i i 组中任取一个数,在第 i + 1 i+1 i+1 组一定能找到一个不大于它的数,因为如果找不到,那么第 i + 1 i + 1 i+1 组的数应该接在第 i i i 组上,从而矛盾。因此从第一组开始一定可以找到一个单调不升的子序列 K K K。设最长不升子序列长度为 P P P,则有 K ≤ P K \leq P K≤P ;又因为最长不升子序列中任意两个不在同一组内,则有 P ≥ K P \geq K P≥K,所以 K = P K=P K=P。
int a[maxn], b[maxn], f[maxn];
int solve() {
int cnt = 0;
for(int i = 1; i <= n; i++) {
int l = 1, r = cnt, pos = 0;
while(l <= r) {
int mid = (l + r) >> 1;
if(f[mid] <= a[i]) {
pos = mid;
r = mid - 1;
} else l = mid + 1;
}
if(!pos) f[++cnt] = a[i];
else f[pos] = a[i];
}
return cnt;
}
同理本题只需要求一次最长不升子序列以及最长上升子序列。
#include <bits/stdc++.h>
using namespace std;
#define ENDL "\n"
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = 1e6 + 10;
const int Mod = 1e6 + 3;
int n;
int a[maxn], d[maxn];
void LIS() {
int len = 0;
d[0] = inf;
for(int i = 1; i <= n; i++) {
if(a[i] <= d[len]) {
d[++len] = a[i];
continue;
}
int l = 1, r = len, pos;
while(l <= r) {
int mid = (l + r) >> 1;
if(d[mid] < a[i]) {
pos = mid;
r = mid - 1;
} else l = mid + 1;
}
d[pos] = a[i];
}
cout << len << endl;
}
void solve() {
int len = 0;
d[0] = 0;
for(int i = 1; i <= n; i++) {
if(a[i] > d[len]) {
d[++len] = a[i];
continue;
}
int pos = lower_bound(d + 1, d + 1 + len, a[i]) - d;
d[pos] = a[i];
}
cout << len << endl;
}
int main() {
// ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int x;
while(cin >> x) {
if(x == 0) break;
a[++n] = x;
}
LIS();
solve();
return 0;
}