兩側端口 1..p 自上而下編號,左側第 i 個端口連接右側第 a[i] 個端口。兩信號交叉當且僅當連線線段相交。求最大無交叉信號數。
關鍵洞察:若選擇索引 i1 < i2 < ... < ik 使得 a[i1] < a[i2] < ... < a[ik],則對應連線不相交。故問題等價於求序列 a 的最長嚴格遞增子序列 (LIS) 長度。
維護陣列 d[len]:長度為 len 的上升子序列的最小結尾值。遍歷序列每個元素 x:
x > d[len],延長序列:d[++len] = x。d[1..len] 中二分查找第一個 ≥ x 的位置 pos,更新 d[pos] = x(不改變長度,但優化結尾值,利於後續延長)。d 陣列始終嚴格遞增,故可用二分查找。最終 len 即為 LIS 長度。
序列:3 2 4 6 5 7 3
| 步驟 | 元素 | d 陣列變化 | len |
|---|---|---|---|
| 1 | 3 | [3] | 1 |
| 2 | 2 | [2] | 1 |
| 3 | 4 | [2, 4] | 2 |
| 4 | 6 | [2, 4, 6] | 3 |
| 5 | 5 | [2, 4, 5] | 3 |
| 6 | 7 | [2, 4, 5, 7] | 4 |
| 7 | 3 | [2, 3, 5, 7] | 4 |
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 40005;
int a[MAXN], d[MAXN];
int main() {
int T; scanf("%d", &T);
while (T--) {
int p; scanf("%d", &p);
for (int i = 0; i < p; ++i) scanf("%d", &a[i]);
int len = 0;
for (int i = 0; i < p; ++i) {
int pos = lower_bound(d + 1, d + len + 1, a[i]) - d;
d[pos] = a[i];
if (pos > len) len = pos;
}
printf("%d\n", len);
}
return 0;
}
int pos = 1, l = 1, r = len;
while (l <= r) {
int mid = (l + r) >> 1;
if (d[mid] < a[i]) pos = mid + 1, l = mid + 1;
else r = mid - 1;
}
d[pos] = a[i];
if (pos > len) len = pos;