题解 U146773 【数据结构一】

· · 个人记录

疑似整体二分,思路上有很大的启发性,值得写一篇题解。

我愿称之为 “整体分治”

似乎这东西其实就是线段树分治?

实则在顶风作案

原题链接

【题意简述】

给定一个序列,每一次询问一个区间,求这个区间 所有子区间的最大值与最小值之积 之和。

n, Q \le 10^5

【思路】

碰到这一类题,一种可行的思路是单调栈,我们这里不考虑这一种套路。

考虑分治。

首先我们对于每一个询问,一种很 naive 的分治套路是把询问分成左右两半,然后拆成“左边一半的贡献+右边一半的贡献+左右之间的贡献”这三部分,下文称这个把序列分成的位置为 “分治断点”,显然这个点可以任意取(这一点很重要)。

这么搞显然是不行的,复杂度会爆炸。

于是我们利用类似整体二分的方法,来一个整体分治。

我们用一个类似于线段树的结构,每一个结点表示一个区间,储存所有 被这个区间完全包含 的询问,显然应该首先在根结点上储存所有询问。

然后我们钦定这个区间的 mid 即区间中点为这个区间内所有询问的分治断点,对于不经过这个点的询问直接下传到当前区间的左右两半子区间即可。

那么剩下的所有点都必然经过这个 mid ,我们考虑怎么统一处理以达到全局复杂度的正确。

首先不难发现,对于一个区间 [L,R] 中的一个询问 (l,r) ,若这个询问经过了区间中点,那么它必然可以被分治为 (l, mid), (mid +1 , r) 这两个子询问加上一个 [l, mid]-[mid+1,r] 的询问。那么前面两部分同样暴力下传即可,后文会证明其复杂度正确。

于是,现在我们要处理的是一堆横跨了 mid 的询问。

显然 mid 的左右两边等价,我们考虑左端点收集贡献右端点提供贡献。

故我们要遍历 [mid+1,r] 区间内的所有右端点,考虑其对于对应的左端点会产生怎样的贡献,显然这部分是 O(\text{区间长度}) 的,整体下来就是 O(n\log n) 的。

我们现在考虑某 一个 确定的右端点 r'

不难发现,此时对于一个左端点 l' 的贡献大致可以被分为以下四类:

(为方便理解,下文中记区间 [l,r] 最小值最大值分别为 \min[l,r],\max[l,r]

  1. \min[l',r'] 出现在 [l',mid] 中。
  2. \max[l',r'] 出现在 [l',mid] 中。

于是我们对于 [L, mid] 区间记录其后缀 \min, \max 值,其中 \min[i,mid],\max[i,mid] 分别记为 Mn[i],Mx[i] ,这一部分显然每一个区间都可以 O(\text{区间长度}) 求出,那么整体是 O(n\log n) 的。

同时我们记录 \min[mid + 1, r'], \max[mid + 1, r'] 分别为 Min, Max ,这个显然可以边扫边求,复杂度不变。

然后我们发现这四部分分别满足的条件是:

  1. Mn[l']<Min,Mx[l']>Max
  2. Mn[l']<Min,Mx[l']\le Max
  3. Mn[l']\ge Min,Mx[l']>Max
  4. Mn[l']\ge Min,Mx[l']\le Max

由于我们不难发现 Mn, Mx 两个数组分别单调递减/增, Min,Max 两个变量同理,所以这个东西具有单调性。

故每一部分的区间是连续的,可以一起求。

并且显然,这个东西的分布从左到右长这样:

[1][2/3][4]

其中 2, 3 显然一次只能满足一个存在。

于是我们考虑记录 3 个指针 p1, p2,p3 分别记录 “最左侧的满足条件 2, 3, 4 ” 的点。

由于 Max 必然越来越大, Min 必然越来越小,可以理解成 2, 3, 4 的满足条件越来越容易实现,故 p1,p2,p3 必然只会单调向左移动,那么单结点复杂度 O(\text{区间长度}) ,复杂度总体 O(n\log n)

于是我们就确定了每一类数的出现区间。

那么接下来考虑每一类数的贡献。

不难发现每一类数产生的贡献为:

  1. Mn[l']\times Mx[l']
  2. Mn[l']\times Max
  3. Min\times Mx[l']
  4. Min\times Max

不难发现每一段区间的贡献都可以表示为“一个不变的常数乘上一个变化的系数”的形式,故可以用线段树去维护,复杂度一次 O(\log n) ,故总体复杂度 O(n\log^2 n)

查询贡献时,考虑一个询问的右端点是否与当前扫到的右端点重合,若重合,则当前区间 [l',mid] 中的四种贡献之和便为该询问在该区间内跨越中点的答案。因此我们需要让询问的右端点单调不减,故需要排序。具体查询在线段树上区间查询即可,复杂度一次查询 O(\log n)

但我们如何保证询问数的总和复杂度正确呢?

考虑对于一个询问,若它的询问区间与所在的结点所表示的区间相同,那么显然可以直接返回而不下传。

这样我们就得到了一个类似线段树的结构,每一个询问都最多只会访问到 \log 级别的结点,故可以认为总询问数 O(n\log n)

于是我们达到了总体复杂度 O(n\log^2 n) ,非常优秀。

【代码】

#include <cstdio>
#include <vector>
#include <algorithm>
using std :: vector;
using std :: sort;
template <typename T>
inline void read(T &x){
    x = 0;int fu = 1;
    char c = getchar();
    while(c > 57 || c < 48){
        if(c == 45) fu = -1;
        c = getchar();
    }
    while(c <= 57 && c >= 48){
        x = (x << 3) + (x << 1) + c - 48;
        c = getchar();
    }
    x *= fu;
}
template <typename T>
inline void fprint(T x){
    if(x < 0) putchar(45), x = -x;
    if(x > 9) fprint(x / 10);
    putchar(x % 10 + 48);
}
template <typename T>
inline void fprint(T x, char ch){
    fprint(x);putchar(ch);
}
template <typename T>
inline T mn(T x, T y) {return x < y ? x : y;}
template <typename T>
inline T mx(T x, T y) {return x > y ? x : y;}
template <typename T>
inline void chmin(T &x, T y) {(x > y) && (x = y);}
template <typename T>
inline void chmax(T &x, T y) {(x < y) && (x = y);}
#define MAXN 100005
struct Quest{
    int l, r, id;
    Quest (int l, int r, int id) : l(l), r(r), id(id) {}
    inline bool operator < (const Quest &tmp) const{return r < tmp.r;}
};
typedef vector <Quest> vec;
int n, m;
int a[MAXN];
const int MOD = 1000000007;
#define LSON rt << 1, l, mid
#define RSON rt << 1 | 1, mid + 1, r
struct SegTree{
    int t[MAXN << 2], tag[MAXN << 2], fac[MAXN << 2];
    int base[MAXN];
    inline void pushup(int rt){t[rt] = (t[rt << 1] + t[rt << 1 | 1]) % MOD;}
    void build(int rt, int l, int r){
        t[rt] = tag[rt] = 0;
        if(l == r){
            fac[rt] = base[l];
            return ;
        }
        int mid = (l + r) >> 1;
        build(LSON);build(RSON);
        fac[rt] = (fac[rt << 1] + fac[rt << 1 | 1]) % MOD;
    }
    inline void update(int rt, int val){
        t[rt] = (1ll * t[rt] + 1ll * fac[rt] * val) % MOD;
        tag[rt] = (tag[rt] + val) % MOD;
    }
    inline void pushdown(int rt){
        if(tag[rt]){
            update(rt << 1, tag[rt]);
            update(rt << 1 | 1, tag[rt]);
            tag[rt] = 0;
        }
    }
    void modify(int rt, int l, int r, int x, int y, int z){
        if(x <= l && r <= y) return update(rt, z);
        pushdown(rt);
        int mid = (l + r) >> 1;
        if(x <= mid) modify(LSON, x, y, z);
        if(y > mid) modify(RSON, x, y, z);
        pushup(rt);
    }
    int query(int rt, int l, int r, int x, int y){
        if(x <= l && r <= y) return t[rt];
        pushdown(rt);
        int mid = (l + r) >> 1, ret = 0;
        if(x <= mid) ret = query(LSON, x, y);
        if(y > mid) ret = (ret + query(RSON, x, y)) % MOD;
        return ret;
    }
}st[4];
vec v[MAXN << 2];
int ans[MAXN], Mn[MAXN], Mx[MAXN];
int num[MAXN << 2];
#define V v[rt]
void solve(int rt, int l, int r){
    int mid = (l + r) >> 1, sz = V.size();
    if(l == r){
        num[rt] = 1ll * a[l] * a[l] % MOD;
        for (register int i = 0;i < sz;i ++) ans[V[i].id] = (ans[V[i].id] + num[rt]) % MOD;
        return ;
    }
    int j = 0;
    while(j < sz && V[j].r <= mid) ++ j;
    Mn[mid] = Mx[mid] = a[mid];
    for (register int i = mid - 1;i >= l;i --){
        Mn[i] = mn(Mn[i + 1], a[i]);
        Mx[i] = mx(Mx[i + 1], a[i]);
    }
    for (register int i = l;i <= mid;i ++){
        st[0].base[i] = 1ll * Mn[i] * Mx[i] % MOD;st[3].base[i] = 1;
        st[1].base[i] = Mn[i];st[2].base[i] = Mx[i];
    }
    for (register int i = 0;i < 4;i ++) st[i].build(1, l, mid);
    int p1 = mid + 1, p2 = mid + 1, p3 = mid + 1;
    int MX = a[mid + 1], MN = MX;
    for (register int i = mid + 1;i <= r;i ++){
        chmax(MX, a[i]);chmin(MN, a[i]);
        while(p1 > l && MX > Mx[p1 - 1] && MN < Mn[p1 - 1]) -- p1;
        while(p2 > l && MX > Mx[p2 - 1]) -- p2;
        while(p3 > l && MN < Mn[p3 - 1]) -- p3;
        if(p1 <= mid) st[3].modify(1, l, mid, p1, mid, 1ll * MX * MN % MOD);
        if(p3 < p1) st[2].modify(1, l, mid, p3, p1 - 1, MN);
        if(p2 < p1) st[1].modify(1, l, mid, p2, p1 - 1, MX);
        if(mn(p2, p3) > l) st[0].modify(1, l, mid, l, mn(p2, p3) - 1, 1);
        while(j < sz && V[j].r == i){
            if(V[j].l > mid || (V[j].l == l && V[j].r == r)){++ j;continue;}
            for (register int k = 0;k < 4;k ++) ans[V[j].id] = (ans[V[j].id] + st[k].query(1, l, mid, V[j].l, mid)) % MOD;
            ++ j;
        }
    }
    for (register int i = 0;i < sz;i ++){
        if(l == V[i].l && r == V[i].r) continue;
        if(V[i].r <= mid) v[rt << 1].push_back(V[i]);
        else if(V[i].l > mid) v[rt << 1 | 1].push_back(V[i]);
        else{
            v[rt << 1].push_back(Quest(V[i].l, mid, V[i].id));
            v[rt << 1 | 1].push_back(Quest(mid + 1, V[i].r, V[i].id));
        }
    }
    for (register int i = 0;i < 4;i ++) num[rt] = (num[rt] + st[i].query(1, l, mid, l, mid)) % MOD;
    solve(LSON);solve(RSON);
    num[rt] = (num[rt] + (num[rt << 1] + num[rt << 1 | 1]) % MOD) % MOD;
    for (register int i = 0;i < sz;i ++)
        if(l == V[i].l && r == V[i].r) ans[V[i].id] = (ans[V[i].id] + num[rt]) % MOD;
}
int main(){
    read(n);read(m);
    for (register int i = 1;i <= n;i ++) read(a[i]);
    for (register int i = 1;i <= m;i ++){
        int l, r;read(l);read(r);
        v[1].push_back(Quest(l, r, i));
    }
    sort(v[1].begin(), v[1].end());
    solve(1, 1, n);
    for (register int i = 1;i <= m;i ++) fprint(ans[i], 10);
}