[HNOI2016] 序列

[HNOI2016] 序列

image-20230910131947183

题解:ST表 + 莫队

  • 设莫队维护区间[l,r]的答案ans,我们考虑右端点r向右扩张时r:=r+1ans的影响,设min[l,r]代表区间[l,r]中的最小值
  • ans:=ans+min[r,r]+min[r1,r]+min[r2,r]+...+min[l,r]
  • [l,r]中最小值的位置为p,则min[i,r]=a[p],i[l,p],所以ans:=ans+a[p]×(pl+1)+min[r,r]+...+min[p+1,r]
  • 我们令pre[i]为左边比a[i]第一个小的位置,nxt[i]为右边比a[i]第一个小的位置,fr[i]:l=1imin[l,i]fl[i]:r=nimin[i,r]
  • fr[i]=fr[pre[i]]+(ipre[i])×a[i]fl[i]=fl[nxt[i]]+(nxt[i]i)×a[i]
  • 我们发现左端点在[pre[r],r]的最小值都是a[r],那么对答案的贡献为(rpre[r])×a[r]
  • 以此类推,一定存在一个点x,使得pre[x]=p
  • 所以ans:=ans+a[p]×(pl+1)+(xpre[x])×a[x]+...+(rpre[r])×a[r]
  • 容易发现(xpre[x])×a[x]+...+(rpre[r])×a[r]=fr[r]fr[p]
  • 所以ans:=ans+a[p]×(pl+1)+fr[r]fr[p]
  • 那么对于一个端点的扩张我们可以O(1)更新答案
  • 单调栈预处理pre[i],nxt[i]STO(nlogn)预处理,O(1)查询最小值位置,O(n)预处理fl[i],fr[i]
  • 复杂度:O(nlogn+nn)
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;
}
posted @   Zeoy_kkk  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示