題面:給出長度爲n的數列,而後算出其區間和乘區間最小數所能獲得的最大值,而且輸出區間ios
樣例輸入:算法
6數組
3 1 6 4 5 2ui
樣例輸出:spa
60.net
3 5code
原題連接:https://vjudge.net/problem/UVA-1619blog
分析:遞歸
這裏有兩種算法,一種是O(nlogn)的,用st表+遞歸,另外一種是O(n)的,用單調棧。隊列
容易知道對於數列中的每個數做爲相應區間最小值時,雖然這個相應區間不必定惟一的,可是這個最大區間和必定是惟一的。
舉個栗子:
對於數列{0, 0, 0, 0, 0}來講,咱們無論是選擇哪個元素做爲區間最小值,其相應的答案區間都有多種可能,但最大區間和確定都是0(你選取第一個0做爲區間最小值, 那區間能夠是[1, 1],[1, 2],[1, 3]等等,可是區間和最大值確定都是0)。
咱們看原題的話就會發現原題說明是關於這個區間的輸出,咱們輸出其中合法的任何一個就好了。
可是!可是!其實這是在坑你!表面上是spj,實際上並非spj!對於每個給定的數據,其輸出的區間也必須也是要跟他給的一毛同樣才行orz,具體的潛規則我也不是很清楚,反正用單調棧才能AC,不過本着真理至上的原則,仍是兩個算法都講一遍。
================
st表+遞歸解法
這種作法是枚舉區間,而後經過st表O(1)找出區間的最小值,配合前輟和能直接計算出一個可能答案。
那怎麼枚舉區間呢?
先說下,st表保存的應該是區間最小值的下標 。
比較直觀的一點就是當咱們選取這個數列的最小值做爲區間的最小值的時候,這個區間[1, n]確定是合法的,設這個最小值的位置爲k,那麼咱們能夠知道[1, k-1], [k+1, n]這兩個區間是另外兩個該被枚舉的區間,而後找出這兩個區間最小值的位置,又能將區間再次分割。。。每次所劃分的區間所對應的區間最小值所在的位置都是不同的,經過這個遞歸過程,如此一來,咱們就獲得了一個O(n)枚舉區間的方法,之因此這個算法是O(nlogn)只是由於建st表要O(nlogn)罷了,其餘操做要麼O(n)要麼O(1)的,只惋惜實際上這道題不是spj,這種方法跟單調棧的作法雖然都是對的,可是所得區間不必定同樣,否則數據水點的話說不定也能過(x)。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 6 const int N = 1e5 + 10; 7 int Logn[N]; 8 long long a[N]; 9 long long sum[N]; //前輟和 10 int st[N][30]; //記錄區間[l, r]中最小元素下標的st表 11 long long ans; //最終答案 12 int L, R; //最終答案區間 13 14 void pre() { 15 Logn[1] = 0; 16 for (int i = 2; i < N; i++) 17 Logn[i] = Logn[i / 2] + 1; 18 } 19 20 void Build_st(int n) //創建st表 21 { 22 int logn = Logn[n]; 23 for(int j=1; j<=logn ;j++) 24 for (int i = 1; i + (1 << j) - 1 <= n; i++) { 25 if (a[st[i][j - 1]] < a[st[i + (1 << (j - 1))][j - 1]]) 26 st[i][j] = st[i][j - 1]; 27 else st[i][j] = st[i + (1 << (j - 1))][j - 1]; 28 } 29 } 30 31 int Query(int l, int r) 32 { 33 int s = Logn[r - l + 1]; 34 35 if (a[st[l][s]] < a[st[r + 1 - (1 << s)][s]]) return st[l][s]; 36 else return st[r + 1 - (1 << s)][s]; 37 } 38 39 void solve(int l, int r) //遞歸處理 40 { 41 if (l > r) return; 42 43 int m = Query(l, r); 44 long long res = (sum[r] - sum[l-1]) * a[m]; 45 if (res > ans) { 46 ans = res; 47 L = l; 48 R = r; 49 } 50 51 solve(l, m - 1); 52 solve(m + 1, r); 53 } 54 55 int main() 56 { 57 //freopen("data.txt", "r", stdin); 58 //freopen("WA.txt", "w", stdout); 59 ios::sync_with_stdio(false); 60 pre(); 61 int n; 62 while (cin >> n) { 63 ans = -1; 64 65 for (int i = 1; i <= n; i++) { 66 cin >> a[i]; 67 sum[i] = a[i] + sum[i - 1]; 68 st[i][0] = i; 69 } 70 71 Build_st(n); 72 solve(1, n); 73 cout << ans << endl; 74 cout << L << " " << R << endl; 75 cout << endl; 76 } 77 78 return 0; 79 }
================
單調棧解法
單調棧的話作法就很單純了,用數組l[i], r[i]分別保存元素a[i]做爲區間最小值時所能延伸到的最左端跟最右端,用單調棧掃描兩次這兩個數組的值就能所有出來了,實際寫代碼的時候博主寫慣了不當心寫成了單調隊列,不過操做都同樣的。。。
1 #include<iostream> 2 #include<algorithm> 3 #include<queue> 4 using namespace std; 5 6 typedef long long ll; 7 const int Maxn = 1e5 + 10; 8 ll a[Maxn]; 9 ll l[Maxn]; 10 ll r[Maxn]; 11 ll sum[Maxn]; 12 13 int main() 14 { 15 int n; 16 bool first = true; 17 while (cin >> n) { 18 if (first) first = false; 19 else cout << endl; 20 21 for (int i = 1; i <= n; i++) { 22 cin >> a[i]; 23 sum[i] = sum[i - 1] + a[i]; 24 } 25 26 deque <ll > rq, lq; 27 for (int i = 1; i <= n; i++) { 28 while (!rq.empty() && a[rq.back()] > a[i]) { 29 r[rq.back()] = i - 1; 30 rq.pop_back(); 31 } 32 33 rq.push_back(i); 34 } 35 while (!rq.empty()) { 36 r[rq.front()] = n; 37 rq.pop_front(); 38 } 39 40 for (int i = n; i >= 1; i--) { 41 while (!lq.empty() && a[lq.back()] > a[i]) { 42 l[lq.back()] = i + 1; 43 lq.pop_back(); 44 } 45 46 lq.push_back(i); 47 } 48 while (!lq.empty()) { 49 l[lq.front()] = 1; 50 lq.pop_front(); 51 } 52 53 ll ans = 0; 54 ll ans_l = 1, ans_r = 1; 55 56 for(int i=1; i<=n ;i++) 57 if ((sum[r[i]] - sum[l[i] - 1]) * a[i] > ans) { 58 ans = (sum[r[i]] - sum[l[i] - 1]) * a[i]; 59 ans_l = l[i]; 60 ans_r = r[i]; 61 } 62 63 cout << ans << endl; 64 cout << ans_l << " " << ans_r << endl; 65 } 66 67 return 0; 68 }