首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

HDU 1950:最長上升子序列 (LIS) 經典應用——信號布線無交叉最大數

     編輯:關於C++

問題轉化

兩側端口 1..p 自上而下編號,左側第 i 個端口連接右側第 a[i] 個端口。兩信號交叉當且僅當連線線段相交。求最大無交叉信號數。

關鍵洞察:若選擇索引 i1 < i2 < ... < ik 使得 a[i1] < a[i2] < ... < a[ik],則對應連線不相交。故問題等價於求序列 a 的最長嚴格遞增子序列 (LIS) 長度。

O(n log n) 演算法:貪心 + 二分 / lower_bound

維護陣列 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
13[3]1
22[2]1
34[2, 4]2
46[2, 4, 6]3
55[2, 4, 5]3
67[2, 4, 5, 7]4
73[2, 3, 5, 7]4

代碼實現 (C++)

#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;
}

手寫二分版本 (無 STL)

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;

複雜度

  • 時間:O(p log p),p ≤ 40000,極快。
  • 空間:O(p)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved