给定一个 H×W 的网格以及 N 个硬币的位置,从 (1,1) 开始每次往下或往右移动一个单元格,求到达 (H,W) 单元格能拾取的最多硬币以及该路径。
Solution
考虑如何转化问题:由于每次只能向下或者向右走,则运动路径在二维平面上看来必然是在横纵坐标上分别非严格单调递增的。换句话说,第 i 个硬币可以被捡到当且仅当上一个捡到的硬币 j 满足 Xj≤Xi 且 Yj≤Yi,很显然的二维偏序求最长上升子序列问题。
题目中的 H,W≤2×105,朴素的 O(n2) 求法无法通过,我们采取更为优秀的二分查找法,定义 fi 表示满足长度为 i 的非严格上升子序列的末尾的最小值,遍历序列时找到 f 数组中最后一个小于等于当前数的下一个数(即大于待选数的第一个数),将这个数替换进去(因为它满足可以构成长度为 i 的子序列并且值更优)。
这里用到了一种贪心的思想,末尾的数更小显然可以匹配更多的数。
这种做法不可以直接求出路径方案,就像最短路一样,它的 f 数组在不断的变化,它代表的不是点的下标而是值的大小,类似最短路路径方案的求法,我们记录每一个更新进来的数的下标以及它的前驱,不断的回退前驱就可以得出路径方案了。
signedmain() { cin >> h >> w >> n; for (int i = 1; i <= n; i ++) cin >> a[i].x >> a[i].y; sort(a + 1, a + 1 + n);
for (int i = 1; i <= n; i ++) { if (q[len] <= a[i].y) { q[++ len] = a[i].y, id[len] = i, pre[i] = len >= 2 ? id[len - 1] : -1; continue; }
int l = 1, r = len, res = 0; while (l <= r) { int mid = l + r >> 1; if (q[mid] > a[i].y) r = mid - 1, res = mid; else l = mid + 1; } q[res] = a[i].y, id[res] = i, pre[i] = res >= 2 ? id[res - 1] : -1; // 第一个数不存在前驱 } cout << len << '\n';
int cur = id[len], idx = len; while (~cur) // 不断寻找前驱直到 cur == { path[idx --] = cur; cur = pre[cur]; }