[bzoj4540][Hnoi2016]序列——单调栈+莫队+RMQ

题目大意:

给定一个序列,每次询问一个区间[L,R]所有子区间的最小值之和。

思路:

考虑莫队如何转移,新增一个端点R,则增加的区间为[L...R-1,R],考虑这些区间新贡献的最小值。
我们把从R开始向左单调下降的序列给求出来,不难发现最小值是由区间内包含的最靠左一个在单调下降序列里的元素的值所决定的。
于是我们利用单调栈求出每一个元素前面第一个小于它的元素\(pre_i\),并求出以这个元素结尾的所有区间的最小值的和\(f_i\),不难发现\(f_i=f_{pre_i}+(i-pre_i)\times a_i\),递推求出来\(f\)数组之后我们要求新增加一个点的贡献,只需要找出区间内最小的点的位置\(m\),对于左端点在m之前的,最小值都是\(a_m\),区间左端点在\([m+1,R]\)的部分我们可以用\(f_i-f_m\)得到,这样就可以用RMQ\(O(1)\)转移了。
但是这样还不够优秀,上面的做法提示我们,如果要求\(\sum_{i=L}^{R}min(a_i..a_R)\)这个东西,可以用RMQ求出最小值的位置之后再利用\(f\)数组相减来解决。
考虑对于一个区间[L,R],最小值的位置为\(m\),那么显然对于\([m+1,R]\)区间的所有点当它们为右端点时前缀最小值为\(a_m\),所以通过\(\sum_{i=m+1}^{R}(f_i-f_m)\)可以得到左右端点都在[m+1,R]的子区间的权值和,同理可得左右端点都在[L,m-1]的子区间的权值和,对于包含m点的区间可以直接通过\((m-L+1)\times (R-m+1)\times a_m\)计算出。
于是得到了这一题较优秀的做法,时间复杂度\(O(n\log n +q)\)。

#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
#define MREP(i,x) for(int i=beg[x],v;v=to[i],i;i=las[i])
#define debug(x) cout<<#x<<"="<<x<<endl
#define pii pair<ll,int>
#define fi first
#define se second
#define mk make_pair
#define pb push_back
typedef long long ll;

using namespace std;

void File(){
    freopen("bzoj4540.in","r",stdin);
    freopen("bzoj4540.out","w",stdout);
}

template<typename T>void read(T &_){
    T __=0,mul=1; char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')mul=-1;
        ch=getchar();
    }
    while(isdigit(ch))__=(__<<1)+(__<<3)+(ch^'0'),ch=getchar();
    _=__*mul;
}

const int maxn=1e5+10;
int n,m,Log[maxn];
ll a[maxn],f1[maxn],g1[maxn],f2[maxn],g2[maxn];
pii st[maxn][21];

int query(int l,int r){
    int d=Log[r-l+1];
    return min(st[l][d],st[r-(1<<d)+1][d]).se;
}

void init(){
    read(n); read(m);
    REP(i,1,n)read(a[i]);

    REP(i,2,n)Log[i]=Log[i/2]+1;
    REP(i,1,n)st[i][0]=mk(a[i],i);
    REP(j,1,20)REP(i,1,n-(1<<j)+1)
        st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);

    int tp;
    ll s[maxn];

    tp=0;
    REP(i,1,n){
        while(tp && a[s[tp]]>a[i])--tp;
        f1[i]=f1[s[tp]]+(i-s[tp])*a[i];
        s[++tp]=i; g1[i]=f1[i]+g1[i-1];
    }
    s[tp=0]=n+1;
    DREP(i,n,1){
        while(tp && a[s[tp]]>a[i])--tp;
        f2[i]=f2[s[tp]]+(s[tp]-i)*a[i];
        s[++tp]=i; g2[i]=f2[i]+g2[i+1];
    }
}

void work(){
    int l,r,p;
    ll ans;
    REP(i,1,m){
        read(l); read(r);
        p=query(l,r);
        ans=a[p]*(p-l+1)*(r-p+1);
        ans+=g1[r]-g1[p]-(r-p)*f1[p];
        ans+=g2[l]-g2[p]-(p-l)*f2[p];
        printf("%lld\n",ans);
    }
}

int main(){
    File();
    init();
    work();
    return 0;
}

原文地址:https://www.cnblogs.com/ylsoi/p/10090222.html

时间: 2024-10-06 00:07:13

[bzoj4540][Hnoi2016]序列——单调栈+莫队+RMQ的相关文章

【bzoj4540】[Hnoi2016]序列 单调栈+离线+扫描线+树状数组区间修改

题目描述 给出一个序列,多次询问一个区间的所有子区间最小值之和. 输入 输入文件的第一行包含两个整数n和q,分别代表序列长度和询问数.接下来一行,包含n个整数,以空格隔开,第i个整数为ai,即序列第i个元素的值.接下来q行,每行包含两个整数l和r,代表一次询问. 输出 对于每次询问,输出一行,代表询问的答案. 样例输入 5 5 5 2 4 1 3 1 5 1 3 2 4 3 5 2 5 样例输出 28 17 11 11 17 题解 单调栈+离线+扫描线+树状数组区间修改 首先把使用单调栈找出每个

【DFS序列】【莫队算法】【权值分块】bzoj2809 [Apio2012]dispatching

题意:在树中找到一个点i,并且找到这个点子树中的一些点组成一个集合,使得集合中的所有点的c之和不超过M,且Li*集合中元素个数和最大 首先,我们将树处理出dfs序,将子树询问转化成区间询问. 然后我们发现,对于单一节点来说,“使得集合中的所有点的c之和不超过M,且Li*集合中元素个数和最大”可以贪心地搞,即优先选择c较小的点.(<--这正是主席树/权值线段树/权值分块的工作) 但是我们需要枚举所有节点,从他们中选一个最大的. 既然有dfs序了,那么就是无修改的区间询问咯.(<--莫队的工作)

bzoj4540: [Hnoi2016]序列

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

[WC2013][UOJ58]糖果公园 莫队算法

这道题有毒!!!!!!!!!!!!!!!!!! 先贴个题面吧QwQ #58. [WC2013]糖果公园 Candyland 有一座糖果公园,公园里不仅有美丽的风景.好玩的游乐项目,还有许多免费糖果的发放点,这引来了许多贪吃的小朋友来糖果公园玩. 糖果公园的结构十分奇特,它由 nn 个游览点构成,每个游览点都有一个糖果发放处,我们可以依次将游览点编号为 11 至 nn.有 n?1n?1 条双向道路连接着这些游览点,并且整个糖果公园都是连通的,即从任何一个游览点出发都可以通过这些道路到达公园里的所有

【BZOJ-3757】苹果树 块状树 + 树上莫队

3757: 苹果树 Time Limit: 20 Sec  Memory Limit: 256 MBSubmit: 1305  Solved: 503[Submit][Status][Discuss] Description 神犇家门口种了一棵苹果树.苹果树作为一棵树,当然是呈树状结构,每根树枝连接两个苹果,每个苹果都可以沿着一条由树枝构成的路径连到树根,而且这样的路径只存在一条.由于这棵苹果树是神犇种的,所以苹果都发生了变异,变成了各种各样的颜色.我们用一个到n之间的正整数来表示一种颜色.树上

关于莫队算法的总结

莫队算法是用来骗分的……这个算法的使用前提是 在不强制在线的情况下,对于[l,r],[l',r']的区间询问,我们需要要O(|l-l'|+|r-r'|)次基本操作从[l,r]转移得到[l',r']的答案 可以发现这就是个高能暴力,只不过因为转移方向的优越带来比裸暴力更优的时空复杂度 如果说cdq分治是花费了复杂度干掉了在线,那么莫队就是花费复杂度干掉了区间 对于所有的询问,把当前询问包含的元素叫作当前的处理集合,然后暴力转移维护处理集合来解决下一个区间的询问 首先是最基本的:序列上应用莫队 现在

P2336 [SCOI2012]喵星球上的点名(后缀自动机+莫队)

P2336 [SCOI2012]喵星球上的点名 #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<map> #define rint register int using namespace std; int read(){ char c=getchar();int x=0; while(

【BZOJ4540】【HNOI2016】序列(莫队)

[BZOJ4540][HNOI2016]序列(莫队) 题面 BZOJ 洛谷 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个子序

csp-s模拟测试50(9.22)「施工(单调栈优化DP)」&#183;「蔬菜(二维莫队???)」&#183;「联盟(树上直径)」

改了两天,终于将T1,T3毒瘤题改完了... T1 施工(单调栈优化DP) 考场上只想到了n*hmaxn*hmaxn的DP,用线段树优化一下变成n*hmaxn*log但显然不是正解 正解是很**的单调栈 可以想象到最优情况一定是将两端高于中间的一段平原填成一段平的坑,不然如果坑内存在高度差那么我们即使只将一部分抬升也肯定没有用处,并且如果中间的坑已经高于了两端,再向上升也肯定不优,然后就中间的坑可以很很小,也可以很长,对于这个模型我们首先想到n^2*h的DP 设当前表示的f[i]表示当前到了i节