参考:http://hi.baidu.com/zhuangxie1013/item/b83773f0e30d1d18d7ff8cbc
偏序集的Dilworth定理:
令(X,≤)是一个有限偏序集,并令m是反链的最大的大小,则X可以被划分成m个但不能再少的链.
给定n个二元组(x, y),问存在最少多少个划分使得每个划分里面的二元组都满足x1<=x2并且y1<=y2。
如果定义x1<=x2&&y1<=y2为偏序关系的话,那么问题就转化成求这个集合的链的最少划分数.可以通过找最长反链长度来解决,这里的反链关系是x1>x2||y1>y2,如果把n个二元组按照x递增排序,相同的x按照y递增排序,那么我们只需对y找到一个最长上升子序列就是所求的答案,复杂度O(n^2)或O(nlogn).对于相同的x之所以按照y递增排序是因为这里偏序关系带等号,这样相同的x其实可以划分到一起,把y按照递增排序就可以使得相同的x最多只选择一个y.
还有的题目要求满足x1<x2&&y1<y2,这就需要把偏序关系相应修改.修改之后对于相同的x,每一个都会被划分到不同的集合(因为相等是不满足偏序关系的),所以这里的排序关系要改一下,x相同的y要按照降序排列(如果y不是降序的话,假设y1<y2,我们很有可能先去放下y1,然后在把y2放在y1的后面,这样事实上是不合法的),这样求一个最长不递增子序列就是答案,y递减保证可能会有多个x相同的二元组选入到结果中.
对于本题,先按照w升序排序,当w相等时再按照h降序排序,然后就是对序列h求解最长不上升子序列了.
求最长不上升子序列,一般解法为O(n^2)的动规解法,可以通过二分的方法来优化,优化方法为通过一个数组(stack)来记录当前长度为i的子序列的最大值为多少,对于一个新来的值v,通过二分查找的方法,找到stack中第一个比它小的数(第k个数),更新它,表示长度为k的子序列的当前最大值v。这样时间复杂度变为O(nlogn)。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define INF 10005
#define MAX 20005
struct Doll
{
int w, h;
}doll[MAX];
int stack[MAX], n;
int cmp(Doll d1, Doll d2)
{
if(d1.w == d2.w)
return d1.h > d2.h;
return d1.w < d2.w;
}
int bi_search(int low, int high, int key)
{
int mid;
while(low <= high)
{
mid = (low+high)>>1;
if(stack[mid] >= key)
low = mid + 1;
else
high = mid - 1;
}
return low;
}
int findAns()
{
int top = 1;
stack[0] = INF, stack[1] = doll[0].h;
for(int i=1; i<n; i++)
{
int pos = bi_search(0, top, doll[i].h);
if(pos > top)
stack[++top] = doll[i].h;
else
stack[pos] = doll[i].h;
}
return top;
}
int main()
{
int t;
scanf("%d", &t);
while(t--)
{
scanf("%d", &n);
for(int i=0; i<n; i++)
scanf("%d %d", &doll[i].w, &doll[i].h);
sort(doll, doll+n, cmp);
memset(stack, 0, sizeof(stack));
printf("%d\n", findAns());
}
return 0;
}