[HNOI2016] 序列

发布时间 2023-09-10 14:10:43作者: Zeoy_kkk

[HNOI2016] 序列

image-20230910131947183

题解:\(ST\)表 + 莫队

  • 设莫队维护区间\([l,r]\)的答案\(ans\),我们考虑右端点\(r\)向右扩张时\(r:=r+1\)\(ans\)的影响,设\(min[l,r]\)代表区间\([l,r]\)中的最小值
  • \(ans :=ans+min[r,r]+min[r-1,r]+min[r-2,r]+...+min[l,r]\)
  • \([l,r]\)中最小值的位置为\(p\),则\(min[i,r] = a[p],i\in[l,p]\),所以\(ans:=ans+a[p]\times(p-l+1)+min[r,r]+...+min[p+1,r]\)
  • 我们令\(pre[i]\)为左边比\(a[i]\)第一个小的位置,\(nxt[i]\)为右边比\(a[i]\)第一个小的位置,\(fr[i]:\sum_{l=1}^{i}min[l,i]\)\(fl[i]:\sum_{r=n}^{i}min[i,r]\)
  • \(fr[i] = fr[pre[i]]+(i-pre[i])\times a[i]\)\(fl[i] = fl[nxt[i]]+(nxt[i]-i)\times a[i]\)
  • 我们发现左端点在\([pre[r],r]\)的最小值都是\(a[r]\),那么对答案的贡献为\((r - pre[r])\times a[r]\)
  • 以此类推,一定存在一个点\(x\),使得\(pre[x] = p\)
  • 所以\(ans:=ans+a[p]\times(p-l+1)+(x-pre[x])\times a[x]+...+(r-pre[r])\times a[r]\)
  • 容易发现\((x-pre[x])\times a[x]+...+(r-pre[r])\times a[r]=fr[r]-fr[p]\)
  • 所以\(ans:=ans+a[p]\times(p-l+1) + fr[r]-fr[p]\)
  • 那么对于一个端点的扩张我们可以\(O(1)\)更新答案
  • 单调栈预处理\(pre[i],nxt[i]\)\(ST\)\(O(nlogn)\)预处理,\(O(1)\)查询最小值位置,\(O(n)\)预处理\(fl[i],fr[i]\)
  • 复杂度:\(O(nlogn + n\sqrt{n})\)
const int N = 1e5 + 10, M = 4e5 + 10;
const int B = sqrt(N) + 1;

int n, q, a[N], nxt[N], pre[N], stk[N], top, fl[N], fr[N];
int id[N], ans[N], Ans, st[N][22], lg2[N];

struct QUERY
{
    int l, r, idx;
    bool operator<(const QUERY &t) const
    {
        if (id[l] == id[t.l])
        {
            if (id[l] & 1)
                return r < t.r;
            else
                return r > t.r;
        }
        else
            return l < t.l;
    }
} qry[N];

void build()
{
    for (int i = 1; i <= n; ++i)
        st[i][0] = i;
    for (int i = 2; i <= n; ++i)
        lg2[i] = lg2[i >> 1] + 1;
    for (int j = 1; j <= 20; ++j)
        for (int i = 1; i + (1ll << j) - 1 <= n; ++i)
            st[i][j] = (a[st[i][j - 1]] < a[st[i + (1ll << (j - 1))][j - 1]] ? st[i][j - 1] : st[i + (1ll << (j - 1))][j - 1]);
}

int query(int l, int r)
{
    int len = lg2[r - l + 1];
    return (a[st[l][len]] < a[st[r - (1ll << len) + 1][len]] ? st[l][len] : st[r - (1ll << len) + 1][len]);
}

void del(int l, int r, int op)
{
    int p = query(l, r);
    if (op == 1)
        Ans -= a[p] * (r - p + 1) + fl[l] - fl[p];
    else
        Ans -= a[p] * (p - l + 1) + fr[r] - fr[p];
}

void add(int l, int r, int op)
{
    int p = query(l, r);
    if (op == 1)
        Ans += a[p] * (r - p + 1) + fl[l] - fl[p];
    else
        Ans += a[p] * (p - l + 1) + fr[r] - fr[p];
}

void solve()
{
    cin >> n >> q;
    for (int i = 1; i <= n; ++i)
        cin >> a[i];
    // 初始化 ST表
    build();
    // 单调栈预处理 pre[i] : 左边第一个比a[i]小的数的位置
    for (int i = 1, top = 0; i <= n; ++i)
    {
        while (top && a[stk[top]] >= a[i])
            top--;
        if (top)
            pre[i] = stk[top];
        else
            pre[i] = 0;
        stk[++top] = i;
    }
    // 单调栈预处理 nxt[i] : 右边第一个比a[i]小的数的位置
    for (int i = n, top = 0; i >= 1; --i)
    {
        while (top && a[stk[top]] >= a[i])
            top--;
        if (top)
            nxt[i] = stk[top];
        else
            nxt[i] = n + 1;
        stk[++top] = i;
    }
    for (int i = 1; i <= n; ++i)
        fr[i] = fr[pre[i]] + a[i] * (i - pre[i]);
    for (int i = n; i >= 1; --i)
        fl[i] = fl[nxt[i]] + a[i] * (nxt[i] - i);
    for (int i = 1; i <= q; ++i)
    {
        cin >> qry[i].l >> qry[i].r;
        qry[i].idx = i;
    }
    for (int i = 1; i <= n; ++i) // 分块
        id[i] = (i - 1) / B + 1;
    sort(qry + 1, qry + q + 1); // 对查询进行排序
    for (int i = 1, l = 1, r = 0; i <= q; ++i)
    {
        while (l > qry[i].l)
            add(--l, r, 1);
        while (r < qry[i].r)
            add(l, ++r, 2);
        while (l < qry[i].l)
            del(l++, r, 1);
        while (r > qry[i].r)
            del(l, r--, 2);
        ans[qry[i].idx] = Ans;
    }
    for (int i = 1; i <= q; ++i)
        cout << ans[i] << endl;
}