1. 程式人生 > >牛客11月1日 區區區間間間 棧求取版+詳解

牛客11月1日 區區區間間間 棧求取版+詳解

題意:

見 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;
}