bzoj 4540: [Hnoi2016]序列 莫队算法+

本文介绍了一种高效算法,用于解决给定序列上多个区间子序列的最小值之和问题。通过预处理和动态规划技巧,实现了O(n√n)的时间复杂度,适用于大数据集。算法核心在于维护左右两端的区间权值和,利用RMQ技术快速查找最小值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

给定长度为n的序列:a1,a2,…,an,记为a[1:n]。类似地,a[l:r](1≤l≤r≤N)是指序列:al,al+1,…,ar-
1,ar。若1≤l≤s≤t≤r≤n,则称a[s:t]是a[l:r]的子序列。现在有q个询问,每个询问给定两个数l和r,1≤l≤r
≤n,求a[l:r]的不同子序列的最小值之和。例如,给定序列5,2,4,1,3,询问给定的两个数为1和3,那么a[1:3]有
6个子序列a[1:1],a[2:2],a[3:3],a[1:2],a[2:3],a[1:3],这6个子序列的最小值之和为5+2+4+2+2+2=17。

Input

输入文件的第一行包含两个整数n和q,分别代表序列长度和询问数。接下来一行,包含n个整数,以空格隔开
,第i个整数为ai,即序列第i个元素的值。接下来q行,每行包含两个整数l和r,代表一次询问。

Output

对于每次询问,输出一行,代表询问的答案。

Sample Input

5 5

5 2 4 1 3

1 5

1 3

2 4

3 5

2 5
Sample Output

28

17

11

11

17
HINT

1 ≤N,Q ≤ 100000,|Ai| ≤ 10^9

分析:
容易想到可以对一端进行插入,我们设f[i]f[i]f[i]表示 l=il=il=ir=[i,n]r=[i,n]r=[i,n]的区间[l,r][l,r][l,r]的权值和。
考虑左端插入一位,也就是求l=xl=xl=xr=[x,y]r=[x,y]r=[x,y]的区间[l,r][l,r][l,r]权值和。
假设ddd[x,y][x,y][x,y]中最小值的位置,那么对于一个区间[x,k][x,k][x,k]的权值一定等于区间[d,k][d,k][d,k]的权值。
所以f[x]−f[d]f[x]-f[d]f[x]f[d]就是l=xl=xl=xr=[x,d−1]r=[x,d-1]r=[x,d1]的区间[l,r][l,r][l,r]权值和,而跨越ddd的区间权值一定为ddd,我们就求出了在左端插入增加的价值,减少的价值同理。
在另一边同理维护ggg,就可以满足右端插入了,然后就可以莫队解决了。
复杂度是O(nn)O(n\sqrt{n})O(nn)的,预处理f,gf,gf,gO(n)O(n)O(n)的,最小值可以rmq。

代码:

/**************************************************************
    Problem: 4540
    User: ypxrain
    Language: C++
    Result: Accepted
    Time:7096 ms
    Memory:13420 kb
****************************************************************/
 
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#define LL long long
 
const int maxn=1e5+7;
 
using namespace std;
 
int n,m,top;
LL f[maxn],g[maxn],ans;
int a[maxn],bel[maxn],lg[maxn];
int r[maxn][18];
int h[maxn];
 
struct rec{
    int num,l,r;
    LL ans;
}q[maxn];
 
void prework()
{
    for (int i=1;i<=n;i++)
    {
        h[++top]=i;
        while ((top>1) && (a[h[top-1]]>a[h[top]])) h[--top]=h[top+1];
        g[i]=g[h[top-1]]+((LL)i-(LL)h[top-1])*(LL)a[i];
    }
    top=0;
    for (int i=n;i>0;i--)
    {
        h[++top]=i;
        while ((top>1) && (a[h[top-1]]>a[h[top]])) h[--top]=h[top+1];
        f[i]=f[h[top-1]]+((LL)h[top-1]-(LL)i)*(LL)a[i];
    }
    int block=trunc(sqrt(n));
    for (int i=1;i<=n;i++) bel[i]=(i-1)/block+1;
    for (int i=1;i<=n;i++) r[i][0]=i;
    for (int j=1;j<18;j++)
    {
        for (int i=1;i<=n-(1<<j)+1;i++)
        {
            if (a[r[i][j-1]]<a[r[i+(1<<(j-1))][j-1]]) r[i][j]=r[i][j-1];
                                                 else r[i][j]=r[i+(1<<(j-1))][j-1];
        }
    }
    for (int i=1;i<=n;i++) lg[i]=trunc(log(i+0.5)/log(2));
}
 
bool cmp1(rec a,rec b)
{
    if (bel[a.l]==bel[b.l]) return a.r<b.r;
    return a.l<b.l;
}
 
bool cmp2(rec a,rec b)
{
    return a.num<b.num;
}
 
int getmin(int x,int y)
{
    int k=lg[y-x+1];
    int le=r[x][k],ri=r[y-(1<<k)+1][k];
    if (a[le]<a[ri]) return le;
                else return ri;
}
 
void ins1(int l,int r,int op)
{
    int d=getmin(l,r);
    LL s=g[r]-g[d]+((LL)d-(LL)l+1)*(LL)a[d];
    ans+=op*s;
}
 
void ins2(int l,int r,int op)
{
    int d=getmin(l,r);
    LL s=f[l]-f[d]+((LL)r-(LL)d+1)*(LL)a[d];
    ans+=op*s;
}
 
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d",&q[i].l,&q[i].r);
        q[i].num=i;
    }
    prework();
    sort(q+1,q+m+1,cmp1);
    int l=1,r=1;
    ans=a[1];
    for (int i=1;i<=m;i++)
    {
        for (;r<q[i].r;r++) ins1(l,r+1,1);
        for (;r>q[i].r;r--) ins1(l,r,-1);
        for (;l>q[i].l;l--) ins2(l-1,r,1);
        for (;l<q[i].l;l++) ins2(l,r,-1);
        q[i].ans=ans;
    }
    sort(q+1,q+m+1,cmp2);
    for (int i=1;i<=m;i++) printf("%lld\n",q[i].ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值