Educational Codeforces Round 64 Div.2 E - Special Segments of Permutation

单调栈/分治

单调栈做法有点玄学。。首先用单调栈预处理出每个数左边第一个大于他的数和右边第一个大于他的数,这样可以确定在一个范围内该数数最大数。

然后从小的范围开始遍历,看看对应的另一个值在不在右边。。

这样复杂度是O(nlogn)而不是O(n^2), 类似启发式合并的证明,然而我太菜了,不知道怎么证明的。。

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define full(a, b) memset(a, b, sizeof a)
using namespace std;
typedef long long ll;
inline int lowbit(int x){ return x & (-x); }
inline int read(){
    int X = 0, w = 0; char ch = 0;
    while(!isdigit(ch)) { w |= ch == '-'; ch = getchar(); }
    while(isdigit(ch)) X = (X << 3) + (X << 1) + (ch ^ 48), ch = getchar();
    return w ? -X : X;
}
inline int gcd(int a, int b){ return a % b ? gcd(b, a % b) : b; }
inline int lcm(int a, int b){ return a / gcd(a, b) * b; }
template<typename T>
inline T max(T x, T y, T z){ return max(max(x, y), z); }
template<typename T>
inline T min(T x, T y, T z){ return min(min(x, y), z); }
template<typename A, typename B, typename C>
inline A fpow(A x, B p, C lyd){
    A ans = 1;
    for(; p; p >>= 1, x = 1LL * x * x % lyd)if(p & 1)ans = 1LL * x * ans % lyd;
    return ans;
}
const int N = 300005;
int s[N], a[N], pos[N], lg[N], rg[N], tot;

int main(){

    int n = read(), ans = 0;
    for(int i = 1; i <= n; i ++){
        a[i] = read(), pos[a[i]] = i;
    }
    a[0] = a[n + 1] = INF;
    for(int i = 0; i <= n; i ++){
        while(tot && a[s[tot]] < a[i]) tot --;
        lg[i] = s[tot], s[++tot] = i;
    }
    tot = 0;
    for(int i = n + 1; i >= 1; i --){
        while(tot && a[s[tot]] < a[i]) tot --;
        rg[i] = s[tot], s[++tot] = i;
    }
    for(int i = 1; i <= n; i ++){
        if(i - lg[i] < rg[i] - i){
            for(int j = lg[i] + 1; j < i; j ++){
                int tmp = pos[a[i] - a[j]];
                if(tmp > i && tmp < rg[i]) ans ++;
            }
        }
        else{
            for(int j = i + 1; j < rg[i]; j ++){
                int tmp = pos[a[i] - a[j]];
                if(tmp < i && tmp > lg[i]) ans ++;
            }
        }
    }
    cout << ans << endl;
    return 0;
}

还有一种分治做法,就好理解多了。

就是在合并区间的时候,统计一下左半部分后缀的最大值和右半部分前缀的最大值,每个位置依次枚举,看看跨越mid部分有没有对应的另一个值,而且跨越了mid部分的最大值也要小于当前的最大值。

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define full(a, b) memset(a, b, sizeof a)
using namespace std;
typedef long long ll;
inline int lowbit(int x){ return x & (-x); }
inline int read(){
    int X = 0, w = 0; char ch = 0;
    while(!isdigit(ch)) { w |= ch == '-'; ch = getchar(); }
    while(isdigit(ch)) X = (X << 3) + (X << 1) + (ch ^ 48), ch = getchar();
    return w ? -X : X;
}
inline int gcd(int a, int b){ return a % b ? gcd(b, a % b) : b; }
inline int lcm(int a, int b){ return a / gcd(a, b) * b; }
template<typename T>
inline T max(T x, T y, T z){ return max(max(x, y), z); }
template<typename T>
inline T min(T x, T y, T z){ return min(min(x, y), z); }
template<typename A, typename B, typename C>
inline A fpow(A x, B p, C lyd){
    A ans = 1;
    for(; p; p >>= 1, x = 1LL * x * x % lyd)if(p & 1)ans = 1LL * x * ans % lyd;
    return ans;
}
const int N = 300005;
int ans, a[N], pos[N], mx[N];

void solve(int l, int r){
    if(r - l + 1 <= 2) return;
    int mid = (l + r) >> 1;
    solve(l, mid), solve(mid + 1, r);
    mx[mid] = a[mid], mx[mid + 1] = a[mid + 1];
    for(int i = mid - 1; i >= l; i --){
        mx[i] = max(mx[i + 1], a[i]);
    }
    for(int i = mid + 2; i <= r; i ++){
        mx[i] = max(mx[i - 1], a[i]);
    }
    for(int i = l; i <= mid; i ++){
        int tmp = pos[mx[i] - a[i]];
        if(tmp > mid && tmp <= r && mx[tmp] < mx[i]) ans ++;
    }
    for(int i = mid + 1; i <= r; i ++){
        int tmp = pos[mx[i] - a[i]];
        if(tmp <= mid && tmp >= l && mx[tmp] < mx[i]) ans ++;
    }
}

int main(){

    int n = read();
    for(int i = 1; i <= n; i ++){
        a[i] = read(), pos[a[i]] = i;
    }
    solve(1, n);
    cout << ans << endl;
    return 0;
}
posted @ 2019-05-05 20:19  清楚少女ひなこ  阅读(138)  评论(0编辑  收藏  举报