RMQ問題:對於長度為N的序列,詢問區間[L,R]中的最值
RMQ問題的幾種解法:
- 普通遍歷查詢,O(1)-O(N)
- 線段樹,O(N)-O(logN)
- DP,O(NlogN)-O(1)
- RMQ標准算法,O(N)-O(1)
簡單介紹:
- 朴素的查詢,不需要任何預處理,但結果是沒有任何已知的信息可以利用,每次都需要從頭遍歷到尾。
- 線段樹,區間問題的神器,用線段樹做比起朴素的暴力查詢要快得多,關鍵在於線段樹使用了分治思想,利用了區間問題的可合並性。任何一個區間最多只需要logN個線段樹上的區間來合並,線段樹上的區間總數目為O(N)個,因此只需要O(N)的預處理就可以將查詢復雜度降到O(logN)。同時線段樹的樹狀結構使得修改時信息更容易維護。
- DP,又叫ST算法,也是利用了分治的思想。任何一個區間都可以由兩個小於當前區間長度的最大的長度為2的冪的區間合並而來,於是預處理出每個點開始所有長度為2的冪的區間最值,那么查詢時就可以由預處理的信息O(1)得到答案。
- RMQ標准算法,利用了神奇的數據結構--笛卡爾樹,笛卡爾樹將區間最值問題轉化為樹上兩個點的LCA問題,而DFS可以將LCA問題轉化為±1RMQ問題,±1RMQ問題又可以利用分塊和動態規划的思想來解決。上述所有預處理,包括笛卡爾樹的建立、DFS序以及±1RMQ的問題的求解都可以在線性時間內完成,查詢時復雜度為O(1)。
標准算法的實現:
- 結構圖:
- 笛卡爾樹的構造算方法:從左至右掃描原序列,並依次插入到笛卡爾樹的右鏈中,使用單調棧復雜度為O(N)。建好樹后,key是二查搜索樹,value是小根堆。
- 最小值與LCA:建好樹后,區間最小值問題便轉化為了LCA問題,下面簡單證明一下:
假設現在詢問[d, f]的最小值,root為d和f的LCA,由笛卡爾樹的性質可知,root是整棵樹表示區間的最小值,而[d, f]是其子區間,所以root不可能比[d, f]中的數小,又因為d和f屬於root的不同子樹(LCA的性質),所以root一定在[d, f]中(笛卡爾樹的性質),故對兩個點a,b,LCA(a, b)就是[a, b]的最小值,證畢。
- ±1RMQ問題:相鄰兩個數相差1或者-1的序列的RMQ問題
- ±1RMQ問題解法:將原長度為N的序列分成2N/logN塊,每塊長度為logN/2,將原來的詢問分解為塊間詢問和塊內詢問。用ST算法在O(N/logN*log(N/logN))=O(N)的時間內處理出塊與塊之間的區間最值信息,可以在O(1)的時間內解決塊與塊之間的詢問。對於塊內的詢問,由於每塊長度為logN/2,相鄰兩個數的差不是1就是-1,於是對於區間最值出現的位置,本質不同的狀態只有2logN/2=√N個,加上邊界,總共狀態數為O(√N*logNlogN),利用遞推在O(√N*logNlogN)的時間內求出所有狀態來,以后可以在O(1)的時間內得到塊內任意區間最值的位置。總復雜度為O(N + √N*logNlogN) ≈ O(N)。
- LCA與±1RMQ的經典轉化就不細說了,詳見代碼
標准RMQ,O(N)-O(1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
struct PlusMinusOneRMQ { const static int N = 223456; const static int M = 15; int blocklen, block, minv[N], dp[N][20], t[N * 20], f[1 << M][M][M], s[N]; void init(int n) { blocklen = max(1, (int)(log(n * 1.0) / log(2.0)) / 2); block = n / blocklen + (n % blocklen > 0); int total = 1 << (blocklen - 1); for (int i = 0; i < total; i ++) { for (int l = 0; l < blocklen; l ++) { f[i][l][l] = l; int now = 0, minv = 0; for (int r = l + 1; r < blocklen; r ++) { f[i][l][r] = f[i][l][r - 1]; if ((1 << (r - 1)) & i) now ++; else { now --; if (now < minv) { minv = now; f[i][l][r] = r; } } } } } int tot = N * 20; t[1] = 0; for (int i = 2; i < tot; i ++) { t[i] = t[i - 1]; if (!(i & (i - 1))) t[i] ++; } } void initmin(int a[], int n) { for (int i = 0; i < n; i ++) { if (i % blocklen == 0) { minv[i / blocklen] = i; s[i / blocklen] = 0; } else { if (a[i] < a[minv[i / blocklen]]) minv[i / blocklen] = i; if (a[i] > a[i - 1]) s[i / blocklen] |= 1 << (i % blocklen - 1); } } for (int i = 0; i < block; i ++) dp[i][0] = minv[i]; for (int j = 1; (1 << j) <= block; j ++) { for (int i = 0; i + (1 << j) - 1 < block; i ++) { int b1 = dp[i][j - 1], b2 = dp[i + (1 << (j - 1))][j - 1]; dp[i][j] = a[b1] < a[b2]? b1 : b2; } } } int querymin(int a[], int L, int R) { int idl = L / blocklen, idr = R / blocklen; if (idl == idr) return idl * blocklen + f[s[idl]][L % blocklen][R % blocklen]; else { int b1 = idl * blocklen + f[s[idl]][L % blocklen][blocklen - 1]; int b2 = idr * blocklen + f[s[idr]][0][R % blocklen]; int buf = a[b1] < a[b2]? b1 : b2; int c = t[idr - idl - 1]; if (idr - idl - 1) { int b1 = dp[idl + 1][c]; int b2 = dp[idr - 1 - (1 << c) + 1][c]; int b = a[b1] < a[b2]? b1 : b2; return a[buf] < a[b]? buf : b; } return buf; } } }; struct CartesianTree { private: struct Node { int key, value, l, r; Node(int key, int value) { this->key = key; this->value = value; l = r = 0; } Node() {} }; Node tree[maxn]; int sz; int S[maxn], top; /** 小根堆 區間最小值*/ public: void build(int a[], int n) { top = 0; tree[0] = Node(-1, 0x80000000);//將根的key和value賦為最小以保持樹根不變 S[top ++] = 0; sz = 0; for (int i = 0; i < n; i ++) { tree[++ sz] = Node(i, a[i]); int last = 0; while (tree[S[top - 1]].value >= tree[sz].value) { last = S[top - 1]; top --; } tree[sz].l = last; tree[S[top - 1]].r = sz; S[top ++] = sz; } } Node &operator [] (const int x) { return tree[x]; } };/** 樹根為定值0,數組從0開始編號 **/ class stdRMQ { public: void work(int a[], int n) { ct.build(a, n); dfs_clock = 0; dfs(0, 0); rmq.init(dfs_clock); rmq.initmin(depseq, dfs_clock); } int query(int L, int R) { int cl = clk[L], cr = clk[R]; if (cl > cr) swap(cl, cr); return value[rmq.querymin(depseq, cl, cr)]; } private: CartesianTree ct; PlusMinusOneRMQ rmq; int dfs_clock, clk[maxn], value[maxn << 1], depseq[maxn << 1]; void dfs(int rt, int d) { clk[ct[rt].key] = dfs_clock; depseq[dfs_clock] = d; value[dfs_clock ++] = ct[rt].value; if (ct[rt].l) { dfs(ct[rt].l, d + 1); depseq[dfs_clock] = d; value[dfs_clock ++] = ct[rt].value; } if (ct[rt].r) { dfs(ct[rt].r, d + 1); depseq[dfs_clock] = d; value[dfs_clock ++] = ct[rt].value; } } }; |