這一題很容易抽象成最長上升子序列問題:
最長上升子序列問題:
給出一個由n個數組成的序列x[1..n],找出它的最長單調上升子序列。即求最大的m和a1,
a2……,am,使得a1<a2<……<am且x[a1]<x[a2]<……<x[am]。
動態規劃求解思路分析:(O(n^2))
經典的O(n^2)的動態規划算法,設A[i]表示序列中的第i個數,F[i]表示從1到i這一段中以i結尾的最長上升子序列的長度,初始時設F[i] = 0(i = 1, 2, ..., len(A))。則有動態規劃方程:F[i] = max{1, F[j] + 1} (j = 1, 2, ..., i - 1, 且A[j] < A[i])。
貪心+二分尋找:(O(nlogn))
開闢一個棧,每次取棧頂元素s和讀到的元素a做比較,如果a>s, 則加入棧;如果a<s,則二分尋找棧中的比a大的第1個數,並替換。 最後序列長度為棧的長度。
這也是很好理解的,對x和y,如果x<y且E[y]<E[x],用E[x]替換 E[y],此時的最長序列長度沒有改變但序列Q的''潛力''增大。
舉例:原序列為1,5,8,3,6,7
棧為1,5,8,此時讀到3,則用3替換5,得到棧中元素為1,3,8, 再讀6,用6替換8,得到1,3,6,再讀7,得到最終棧為1,3,6,7 ,最長遞增子序列為長度4。
用貪心+二分的方法很容易類比出來,需要注意的就是二分的寫法,而且需要輸出最長上升子序列時,直接輸出棧中的元素就行了。
1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 const int MAXN = 500000 + 5; 5 int s[MAXN]; //s[]數組用來類比棧 6 int binsearch(int *a, int l, int r, int val) 7 { 8 while (l <= r) 9 {10 int mid = (l + r) >> 1;11 if (val < a[mid])12 r = mid - 1;13 else if (val > a[mid])14 l = mid + 1;15 else16 return mid;17 }18 return l;19 }20 int main()21 {22 int n, p, r, a[MAXN];23 s[0] = INT_MIN;24 int icase = 0;25 while (scanf("%d", &n) != EOF)26 {27 ++icase;28 for (int i = 0; i < n; ++i)29 {30 scanf("%d %d", &p, &r);31 a[p] = r;32 }33 int val, t = 1; //t標記數組最後一個元素的下一位置,起始為134 for (int i = 1; i <= n; ++i)35 {36 val = a[i];37 if (val > s[t-1])38 s[t++] = val;39 else40 s[binsearch(s, 1, t - 1, val)] = val;41 }42 if (t - 1 == 1)43 printf("Case %d:\nMy king, at most %d road can be built.\n\n", icase, t - 1);44 else45 printf("Case %d:\nMy king, at most %d roads can be built.\n\n", icase, t - 1);46 }47 return 0;48 }