标签:组合数学 从后往前 tor for tools 要求 pes 指针 bool
3 5 4 9 5 2 2 1 3 5 1 4 3 2 2 1 1 2 2 3 1 3 2 2 3 1
2 1 3
这道题看起来束手无策,但是如果你知道组合数学中神奇的狄尔沃斯定理的话,一切就迎刃而解。
狄尔沃斯定理: 对偏序集<A,≤>,设A中最长链的长度是n,则将A中元素分成不相交的反链,反链个数至少是n。
对于反链:设<A>是一个偏序集合,在A的一个子集中,如果每两个元素都是有关系的,则称这个子集为链。在A的一个子集中,如果每两个元素都是无关的,则称这个子集为反链。
证明由于鄙人数学垃圾,大家可以去看看百度百科的证明。
题意要求满足对于后面的x,y和前一个x‘,y‘有x >= x‘且 y >= y‘,那么从后往前来看就是一个不上升序列,因此题意是要求我们求得这个最长不上升子序列的最小个数。
那么在这里,反链就是最长上升子序列,而最长上升子序列的长度也就是最长不上升子序列的最小个数,也就是我们要求的答案,注意长度和序列个数不一样。
我们可以先排序,因为我们要得到最长上升子序列的长度并且我们要从后往前遍历,就应该按照从小到大排序,对于二维,我们也可以采取排x,这样可以保证,倒序遍历的时候x一定能小于等于之前的,也就是x不会有问题,我们就只要比较y即可。
在我们倒序进行排列的时候,我们先把建立一个数组dp(该数组为最长上升序列)初始化为INF,然后把y与最小的大于等于y的值找出(因为等于在上升序列中不会改变序列长度,得要是大于),那么把y与其替换,这样可以使得如此操作后找到最长的上升子序列的个数(也就是数组的个数,因为这样替换使得如果没有比y大的数,那么就会找到INF,将其替换,这样序列的长度就多了一个;如果有比y大的数,保证该数组的原本第一个比y大的值可以变小,但是保持dp的递增的序列不变,如果后面存在现有的序列还大的递增序列,那么就会被替换,这样循环就能找到最长的上升序列。
(结合白书P65仔细想想为什么这样操作就能找到最大上升子序列)
找到了之后就好办了,求出该序列的长度,就能得到最长不上升序列的个数,也是题目要求的答案。
AC代码:
#include <stdio.h> #include <algorithm> using namespace std; struct Node{ int x; int y; friend bool operator <(Node x, Node y){ return x.x < y.x; } }sticks[5005]; const int INF= 0x3fffffff; int dp[5005]; int main(void) { int t, n; scanf("%d", &t); for(int times = 0; times < t; times++) { scanf("%d", &n); fill(dp, dp + 5005, INF); for(int i = 0; i < n; i++) { scanf("%d %d", &sticks[i].x, &sticks[i].y); } sort(sticks, sticks + n); for(int i = n - 1; i >= 0; i--) { //lower_bound就是找到在当前序列中第一个大于等于sticks[i].y的位置,它是一个地址减去dp的首地址就得到了该位置的dp数组的下标,实际上这行和下一行可以合并为*lower_bound(dp, dp + n, sticks[i].y) = sticks[i].y的,为了怕指针难看懂就这样写了。 int p = lower_bound(dp, dp + n, sticks[i].y) - dp; dp[p] = sticks[i].y; } printf("%d\n", lower_bound(dp, dp + n, INF) - dp);//最后最长上升子序列的长度就是最长不上升子序列的最小个数,也就是答案 } }
挑战程序设计竞赛2.3:Wooden Sticks POJ - 1065
标签:组合数学 从后往前 tor for tools 要求 pes 指针 bool
原文地址:https://www.cnblogs.com/jacobfun/p/12234094.html