今天早上准备看一波uestc的dp,看到第一道例题的时候发现我竟然不会QAQ,心想清早看的第一题我都不会,甚是郁闷,然后又去百度百度……发现了一个Dilworth定理,然后一直怼一直怼。
结论:对于一个偏序集,最少的chain的个数等于最长antichain的长度,最少的antichain的个数等于最长chain的长度。
比如对于一个二元组,定义偏序关系"≤",当且仅当(a.i < b.i) && (a.j < b.j)时,a 与 b可比,但是会发现有些二元组是没法比较的,称之为偏序集。
与她相对的东西叫做全序集,就是任意两个元素都可以比较,还有就是良序集,有最小元素,比如正整数就是一个良序集。
看了半天QAQ,依然没有看懂证明,证明好久懂了,再回来补吧,接下来我讲一讲这个定理的实际的应用。
一、
codevs 1044 导弹拦截:
题意:依次给出每个导弹飞来的高度,有一种反导弹装置可以将导弹击毁,但是中途过程中,反导弹装置的高度不能升高,求某一台装置最多击落多少个导弹?请问至少需要多少台装置可以拦截所有导弹?
题解:
chain : xi ≤ xj 当且仅当 (i <= j && h[i] >= h[j])
antichain : (h[i] < h[j] && i <= j) || (h[i] >= h[j] && i > j)
如果按照i <= j 排序,那么antichain中所有的元素h[i] < h[j],现在的问题就是求这个串最长上升子序列的长度。
代码:
#include <bits/stdc++.h> using namespace std; const int N = 1e6 + 7; int x[N], n = 1, d[N], ans1, ans2; int main () { while (cin >> x[n]) n++; --n; memset (d, 127, sizeof d); for (int i = n; i >= 1; --i) { int p = upper_bound (d + 1, d + 1 + n, x[i]) - d; ans1 = max (ans1, p); d[p] = x[i]; } memset (d, 127, sizeof d); for (int i = 1; i <= n; ++i) { int p = lower_bound (d + 1, d + 1 + n, x[i]) - d; ans2 = max (ans2, p); d[p] = x[i]; } cout << ans1 << endl << ans2; return 0; }
二、
POJ 1065 Wooden Sticks
题意:有n个棍子,每个棍子都有长度和质量,现在有一台机器来处理这些棍子,机器开启需要一个单位的时间,但是比如现在处理到了第 i 根棍子,如果第i + 1 根棍子的长度和质量都大于等于第i根棍子那么将不会消耗时间,求处理完这些棍子的最少时间。
题解:
chain : xi ≤ xj 当且仅当 (xi.m <= xj.m && xi.w <= xj.w)
antichain : (xi.m <= xj.m && xi.w > xj.w) || (xi.m > xj.m && xi.w <= xj.w)
如果按照x.m从小到大排序,那么就应该求最长下降子序列,但是在m相等的情况下要使x.w 按照从小到大的顺序来排,因为这样才能在求子序列的时候避免错误情况。
代码:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int N = 1e5 + 7; int kase, n, d[N]; struct node {int a, b;} x[N]; bool cmp (node x, node y) { if (x.a == y.a) return x.b < y.b; return x.a < y.a; } int main () { scanf ("%d", &kase); while (kase--) { scanf ("%d", &n); for (int i = 1; i <= n; ++i) scanf ("%d%d", &x[i].a, &x[i].b); sort (x + 1, x + 1 + n, cmp); memset (d, 127, sizeof d); int ans = 0; for (int i = n; i >= 1; --i) { int p = lower_bound (d + 1, d + 1 + n, x[i].b) - d; ans = max (ans, p); d[p] = x[i].b; } cout << ans << endl; } return 0; }
poj 3636 Nested Dolls
题意:有n个矩形,如果一个矩形的长宽都小于另一个矩形的长宽,那么这两个矩形可以嵌套在一起,问最少需要几个嵌套组合才能匹配完所有的矩形?
题解:
chain : xi ≤ xj 当且仅当 (xi.l < xj.l && xi.w < xj.w)
antichain : (xi.l <= xj.l && xi.w >= xj.w) || (xi.l >= xj.l && xi.w <= xj.w)
如果按照x.l从小到大排序,那么就是求关于x.w的最长不上升子序列,但是注意的是当x.l相等的时候,要按照w从大到小排序。
代码:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int N = 1e5 + 7; int kase, n, d[N]; struct node {int a, b;} x[N]; bool cmp (node x, node y) { return (x.a < y.a) || (x.a == y.a && x.b > y.b); } int main () { scanf ("%d", &kase); while (kase--) { scanf ("%d", &n); for (int i = 1; i <= n; ++i) scanf ("%d%d", &x[i].a, &x[i].b); sort (x + 1, x + 1 + n, cmp); memset (d, 127, sizeof d); int ans = 0; for (int i = n; i >= 1; --i) { int p = upper_bound (d + 1, d + 1 + n, x[i].b) - d; ans = max (ans, p); d[p] = x[i].b; } cout << ans << endl; } return 0; }
poj 1548 Robots
题意:在一个矩形区域内,一个机器人在左上角出发,每次只能向右向下进行移动,在移动中清理垃圾,一直到右下角,给出所有垃圾的位置,求至少要多少个机器人才能清理完所有垃圾?
题解:
chain : pi ≤ pj (pi.x <= pj.x && pi.y <= pj.y)
antichain : (pi.x <= pj.x && pi.y > pj.y) || (pi.x > pj.x && pi.y <= pj.y)
如果按照p.x排序,那么也就是求关于y的最长下降子序列,需要注意的是当x 想等的时候按照要按照y从小到大排序。
代码:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int N = 1e5 + 7; struct node {int x, y;} p[N]; int n, x, y, d[N]; bool cmp (node a, node b) { return a.x < b. x || (a.x == b.x && a.y < b.y); } int main () { while(true) { n = 0; while (scanf ("%d%d", &x, &y) != EOF) { if (x == 0 && y == 0) break; if (x == -1 && y == -1) return 0; p[++n] = (node) {x, y}; } sort (p + 1, p + 1 + n, cmp); memset (d, 127, sizeof d); int ans = 0; for (int i = n; i >= 1; --i) { int pos = lower_bound(d + 1, d + 1 + n, p[i].y) - d; ans = max (ans, pos); d[pos] = p[i].y; } cout << ans << endl;; } return 0; }
总结:
Dilworth,虽然不懂证明但是确实感受到了一波他的正确性,一般将她转化成DAG,在辨别可比性的时候,把它弄在平面上会比较直观,还有在写cmp的时候要想清楚……