题目
有很多小木棍需要机器处理。每个小木棍有重量和长度两个属性。不断把小木棍放入机器中,如果小木棍(a)放完后放入小木棍(b),那么如果(a.weight<=b.weight and a.length<=b.length),机器就可以以0的时间处理完这根木棍,否则需要花1的时间初始化。最开始也需要一次初始化。给出所有小木棍的信息,求一共需要多久处理完所有的小木棍。
分析
这题的做法有两种做法,其实差不多。
我们考虑贪心,先把小木棍按(weight)递增排序((weight)相等的(length)递增)排序,这样就处理掉了第一维。接下来只要不断地找最长不下降序列即可。复杂度为(O(n^2))
第二种用到了Dilworth定理。这个定理主要处理的是偏序中的问题。
Dilworth定理
定义偏序(ale b)满足:
- 若(ale a)则(a=a)
- 若(ale b and ble a),则(a=b)
- 若(ale b and ble c),则(ale c)
其中符号(le)为定义的比较,不一定是数值大小的比较,也可能是二元组的比较关系,或者是整除关系((ale b)表示(a|b),容易发现这个定义满足上面的三个性质)。
这三个性质分别称为自反性,反对称性和传递性。
定义:
- (a),(b)不可比为(ale b)与(ble a)均不满足(例如定义比较为整除,那么3与6,8与2可比,但3与7,15与8不可比)。
- 链为一列有序的元素,满足(a_ile a_{i+1})。
- 反链为一个集合,满足其中元素两两不可比。
- 极小元在集合中最小。即设极小元为(a),那么若有(ble a),那么可以推出(b=a)。如果没有这样的(b),也称(a)为极小元。所以极小元一定是存在的(除非集合为空)。
定理(都在给定集合(S)与比较关系(C)中讨论):
- (d)为最长链长度,那么集合一定可以被划分成至少(d)个反链。
- (e)为最长反链长度,那么集合一定可以被划分成至少(e)个链。
第二个定理就是(Dilworth)定理。
定理一证明
设最小反链个数为(x)
- 最长链中任意两个元素如果在同一个反链中就会导致反链中有两个元素可比,不符合反链的定义,所以至少需要(d)个反链,即(xge d)。
- 不断从集合中删除极小元(或极小元集合)(A_i),删除(k)次后集合为空,那么每个(A_1,A_2...A_k)都是一组反链,所以有(kge x)。而又因为存在(a_1le a_2le ...a_k),其中(a_iin A_i)(否则早就被删掉了),组成了一个链,所以有(dge k),所以(dge kge x)
- 综上,有(dge x),(xge d),所以有(x=d)。
定理二证明
设最小链个数为(x)
- 最长反链中任意两个元素不能处于同一个链,否则链中有两个元素不可比,根据传递性,这个链不满足定义,故至少需要(e)个链,即(xge e)。
- 将最长反链中每个元素都分入一个集合,考虑剩下的反链中的元素,它们一定与最长反链中的某一个元素可比,否则就会被加入最长反链中。所以我们把每个不在最长反链中的元素放入与它可比的最长反链中的元素的集合中(与它可比的最长反链中的元素一定唯一,否则根据传递性,反链不满足定义),我们就构造出了一种做法,使得最小链个数等于最长反链长度,即(x=e),由上面的证明,我们知道它最小。
在小木棍的问题中,我们要求的其实是最小链个数,由定理二可得它等于最长反链长度。所以问题转化成了求二维偏序中的最长反链长度。我们把序列按第一维递增,第一维相等第二维递增的方式排序,那么我们就只需要,求出第二维的最长下降子序列即可,因为如果第一维不严格递增,第二维严格递减,那么这两个元素就是不可比的,所以构成了反链。第二维的最长下降子序列用动态规划的方法可以(O(n^2))求出。
代码
#include<cstdio>
#include<cctype>
#include<algorithm>
using namespace std;
int read() {
int x=0,f=1;
char c=getchar();
for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
for (;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*f;
}
const int maxn=5e3+10;
int f[maxn];
struct log {
int w,l;
inline bool operator < (const log a) const {
return w==a.w?l<a.l:w<a.w;
}
} a[maxn];
int main() {
#ifndef ONLINE_JUDGE
freopen("test.in","r",stdin);
#endif
int T=read();
while (T--) {
int n=read();
for (int i=1;i<=n;++i) a[i].w=read(),a[i].l=read();
sort(a+1,a+n+1);
int ans=0;
for (int i=1;i<=n;++i) {
f[i]=1;
for (int j=1;j<i;++j) if (a[j].l>a[i].l) f[i]=max(f[i],f[j]+1);
ans=max(ans,f[i]);
}
printf("%d
",ans);
}
return 0;
}