P4755 Beautiful Pair (分治 + 主席树)

题意:1e5的数组 计算有多少对 ai * aj <= max(ai ai+1...aj-1 aj)

题解:在处理这种涉及到区间极值的题时 好像是个套路分治 从级值中间分成两个区间

   从区间短的那边暴力枚举算贡献 然后再分治下去

   可以估计复杂度 一个点最多枚举n/2次 两个点最多枚举n/4次 4个点最多枚举n/8次...

   枚举加起来的复杂度是nlogn

   假设枚举了ai作为一个区间端点 问题就转化为统计极值另一边的区间找 <= zd / ai的个数

   我是用主席树 然后写了个类似整体二分求的 看别人题解直接用的树状数组离线做显然复杂度更优秀

   总共复杂度是nlognlogn

 

#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 5;

ll ans;
int n, cnt, len;
int pre[MAXN];
int a[MAXN], b[MAXN], t[MAXN];
int zd[MAXN][25];
int ls[MAXN << 5], rs[MAXN << 5], sum[MAXN << 5];

int build(int l, int r) {
    int rt = ++cnt;
    int mid = l + r >> 1;
    sum[rt] = 0;
    if(l < r) {
        ls[rt] = build(l, mid);
        rs[rt] = build(mid + 1, r);
    }
    return rt;
}

int add(int o, int l, int r, int k) {
    int rt = ++cnt;
    int mid = l + r >> 1;
    ls[rt] = ls[o]; rs[rt] = rs[o]; sum[rt] = sum[o] + 1;

    if(l < r)
        if(k <= mid) ls[rt] = add(ls[o], l, mid, k);
        else rs[rt] = add(rs[o], mid + 1, r, k);
    return rt;
}

int query(int ql, int qr, int l, int r, int x) {
    if(l == r) return b[l] <= x ? sum[qr] - sum[ql] : 0;

    int mid = l + r >> 1;
    if(x <= b[mid]) return query(ls[ql], ls[qr], l, mid, x);
    else return sum[ls[qr]] - sum[ls[ql]] + query(rs[ql], rs[qr], mid + 1, r, x);
}

void solve(int l, int r) {
    if(l >= r) return;
    int lg = log2(r - l + 1);
    int index;
    if(a[zd[l][lg]] > a[zd[r - (1 << lg) + 1][lg]]) index = zd[l][lg];
    else index = zd[r - (1 << lg) + 1][lg];

    if(index - l < r - index) {
        for(int i = l; i < index; i++) {
            //if(a[i] == 1) ans++;
            int now = a[index] / a[i];
            ans += 1LL * query(t[index], t[r], 1, len, now);
        }
    } else {
        for(int i = index + 1; i <= r; i++) {
            //if(a[i] == 1) ans++;
            int now = a[index] /a[i];
            ans += 1LL * query(t[l - 1], t[index - 1], 1, len, now);
        }
    }
    if(r - l + 1 >= 2) ans += 1LL * (pre[r] - pre[index] + pre[index - 1] - pre[l - 1]); 
    solve(l, index - 1);
    solve(index + 1, r);
}

int main() {
    cnt = 0;
    ans = 0;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]), b[i] = a[i];
    for(int i = 1; i <= n; i++) zd[i][0] = i;
    for(int i = 1; i <= n; i++) {
        pre[i] = pre[i - 1];
        if(a[i] == 1) pre[i]++;
    }

    for(int j = 1; j <= 20; j++)
        for(int i = 1; i + (1 << j) - 1 <= n; i++)
            if(a[zd[i][j - 1]] > a[zd[i + (1 << j - 1)][j - 1]]) zd[i][j] = zd[i][j - 1];
            else zd[i][j] = zd[i + (1 << j - 1)][j - 1];

    sort(b + 1, b + 1 + n);
    len = unique(b + 1, b + 1 + n) - b - 1;
    t[0] = build(1, len);
    for(int i = 1; i <= n; i++) {
        int tt = lower_bound(b + 1, b + 1 + len, a[i]) - b;
        t[i] = add(t[i - 1], 1, len, tt);
    }

    solve(1, n);
    for(int i = 1; i <= n; i++)
        if(a[i] == 1) ans++;
    printf("%lld\n", ans);
    return 0;
}
View Code

 

posted @ 2020-02-11 17:17  lwqq3  阅读(136)  评论(0编辑  收藏  举报