牛客11月1日 區區區間間間 棧求取版+詳解
阿新 • • 發佈:2018-11-13
題意:
見 https://blog.csdn.net/xiang_6/article/details/83653989 本題線段樹解法
思路:
本分程式碼思路較為清奇,也很好
首先由線段樹解法那篇題意我們知道,要求區間最大值的貢獻的時候,我們要知道這個數能輻射的區間範圍,
例如再求某個值V作為最大值的貢獻的時候,我們只要找到其左右兩邊比他大的兩個數(沒有這樣數的時候就是0,n+1兩個邊界),這樣兩個數中間都是比V小的數,也就是V作為最大值的輻射範圍,設當前數V左邊有x1個數比他小,右邊有x2個數比他小,那這個數V貢獻的區間個數為 (x1 + x2 + x1*x2),就可以算當前值的貢獻值了;
這樣的話我們只需要知道每個值的x1,x2就好了,但是沒辦法每個數暴力找,所以:
在求最大值的貢獻的時候:從左到右,相當於維護一個原始值遞減的棧,存放的下標,
在遇到一個新的值W的時候,如果他比棧頂的位置的元素值大的話,那棧頂元素的x1,x2就得到了,然後刪去棧頂元素,
重複此過程,直到W的位放進去後還是符合上述性質的棧,這樣的話相當於求取了每個值的貢獻(當然有很多數的貢獻為0)
在求取最小值的貢獻的時候同理,因為我們要找某個數V 左右兩邊小於他的位置,所以相當於我們要維護一個原始值上升的棧,存放下標;
#include<bits/stdc++.h> using namespace std; #define out fflush(stdout) #define fast ios::sync_with_stdio(0),cin.tie(0); #define FI first #define SE second typedef long long ll; typedef pair<ll,ll> P; const int maxn = 1e5 + 7; const int INF = 0x3f3f3f3f; ll n, a[maxn], tot, ans1, ans2; ll id[maxn]; void init() { scanf("%lld", &n); for(int i = 1; i <= n; ++i) { scanf("%lld", a+i); } id[0] = 0; tot = 0; ans1 = 0, ans2 = 0; } void solve() { for(int i = 1; i <= n; ++i) { while(tot && a[i] >= a[id[tot]]) ans1 += ((id[tot]-id[tot-1]-1) + (i-id[tot]-1) + ((id[tot]-id[tot-1]-1))*((i-id[tot]-1))) * a[id[tot]], tot--; id[++tot] = i; } while(tot) { ans1 += ((id[tot]-id[tot-1]-1) + (n+1-id[tot]-1) + ((id[tot]-id[tot-1]-1))*((n+1-id[tot]-1))) * a[id[tot]], tot--; } id[0] = 0; tot = 0; for(int i = 1; i <= n; ++i) { while(tot && a[i] <= a[id[tot]]) ans2 += ((id[tot]-id[tot-1]-1) + (i-id[tot]-1) + ((id[tot]-id[tot-1]-1))*((i-id[tot]-1))) * a[id[tot]], tot--; id[++tot] = i; } while(tot) { ans2 += ((id[tot]-id[tot-1]-1) + (n+1-id[tot]-1) + ((id[tot]-id[tot-1]-1))*((n+1-id[tot]-1))) * a[id[tot]], tot--; } cout << ans1 - ans2 << endl; } int main() { int T; scanf("%d", &T); while(T--) { init(); solve(); } return 0; }