1215 】陣列的寬度 (單調棧 或 分治 或 單調佇列,算貢獻,需去重)
阿新 • • 發佈:2018-12-13
題幹:
N個整陣列成的陣列,定義子陣列aii..ajj的寬度為:max(ai..aj) - min(ai..aj),求所有子陣列的寬度和。
Input
第1行:1個數N,表示陣列的長度。(1 <= N <= 50000)
第2 - N + 1行:每行1個數,表示陣列中的元素(1 <= Aii <= 50000)
Output
輸出所有子陣列的寬度和。
Sample Input
5 1 2 3 4 5
Sample Output
20
解題報告:
這題顯然不能列舉區間然後分別計算,所以很多技巧根本不需要考慮(比如排序一下?)
然後我們考慮列舉每一個元素,計算他對答案的貢獻。
記得去重,也就是,一邊算 嚴格單調,一邊是 不嚴格單調,這樣就可以保證不重不漏。(像 5 1 2 1 3 3 這樣的樣例,算貢獻時(2,4)這種區間 只需要算一次。所以單調棧的時候一邊是嚴格單調,一邊是不嚴格單調)這個去重的方法很巧妙啊、
AC程式碼:
#include <bits/stdc++.h> #define ll long long using namespace std; const int MAX = 50000 + 5; ll a[MAX],maxx[MAX],minn[MAX]; int l[MAX],r[MAX],L[MAX],R[MAX];//左側第一個比我小的,右側第一個比我小的。 stack<int> sk; int main() { int n; cin>>n; for(int i = 1; i<=n; i++) scanf("%lld",a+i); //從左到右找第一個比我小的,所以從左到右維護一個單調遞增棧。 for(int i = 1; i<=n; i++) { while(!sk.empty() && a[sk.top()] > a[i]) sk.pop(); if(sk.size()) l[i] = sk.top(); else l[i] = 0; sk.push(i); // printf("%d %d\n",i,l[i]); } while(!sk.empty()) sk.pop(); //從右到左找第一個比我小的,所以維護從右向左維護一個單調遞減棧。 for(int i = n; i>=1; i--) { while(!sk.empty() && a[sk.top()] >= a[i]) sk.pop(); if(sk.size()) r[i] = sk.top(); else r[i] = n+1; sk.push(i); // printf("%d %d\n",i,r[i]); } for(int i = 1; i<=n; i++) minn[i] = (r[i] - i - 1) * (i-l[i]-1) + (r[i]-l[i]-1); while(!sk.empty()) sk.pop(); //從左到右找第一個比我大的,所以從左向右維護一個單調遞減棧, for(int i = 1; i<=n; i++) { while(!sk.empty() && a[sk.top()] < a[i]) sk.pop(); if(sk.size()) L[i] = sk.top(); else L[i] = 0; sk.push(i); } while(!sk.empty()) sk.pop(); for(int i = n; i>=1; i--) { while(!sk.empty() && a[sk.top()] <= a[i]) sk.pop(); if(sk.size()) R[i] = sk.top(); else R[i] = n+1; sk.push(i); } for(int i = 1; i<=n; i++) maxx[i] = (R[i] - i-1) * (i-L[i]-1) + (R[i]-L[i]-1); // printf("yingyingying\n"); // for(int i = 1; i<=n; i++) { // printf("%lld %lld\n",minn[i],maxx[i]); // } ll ans = 0; for(int i = 1; i<=n; i++) { ans += a[i] * (maxx[i] - minn[i]); } printf("%lld\n",ans); return 0; }
還有幾個好的題解:(暫時還未看)