洛谷P3246 [HNOI2016]序列

5 篇文章 0 订阅
1 篇文章 0 订阅

题目描述

给定长度为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。

输入输出格式

输入格式:

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

输出格式:

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

输入输出样例

输入样例#1: 
5 5
5 2 4 1 3
1 5
1 3
2 4
3 5
2 5
输出样例#1: 
28 
17 
11 
11 
17

说明

1 <=N,Q <= 100000,|Ai| <= 10^9

原题40%数据是 ST表+二维前缀和,另外20%数据是ST表+分治,这里说100%数据:

ST表+单调栈+莫队

无论如何都要有ST表,这个ST表求的是最小值的下标。

单调栈预处理每个数向左右能成为多大区间的最小值。

然后就是莫队了。。。

莫队的难点就在于状态转移,怎么通过[l,r]的答案推出[l,r+1]的答案。

考虑找到[l,r+1]里的最小值的位置(ST表),记为pos。

[l,r+1]相对于[l,r]其实就多了r-l+2个包括r+1的子序列,

那么[l,pos]的对答案的贡献就等于(pos-l+1)*a[pos]

那么怎么求[pos+1,r]的贡献呢

这里引入一个类似前缀和的东西,

我们用 front[i] 表示a[i]左边第一个比a[i]小的数的位置,next[i] 表示右边,

这个过程显然可以用单调栈O(n)实现

然后开2个数组maxl , maxr。

maxl[i] 表示 [1,i] 的贡献,maxl[i] 可通过maxl[i] = maxl[ front[i] ] + ( i-front[i] ) * val[i]得到。

同理,maxr[i] = maxr[ next[i] ] + ( next[i]-i ) * val[i]。

注意:记得开long long。。。

附代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cstring>
#define MAXN 100010
using namespace std;
int n,m;
int val[MAXN],f[MAXN][30],front[MAXN],next[MAXN];
long long ans[MAXN],maxl[MAXN],maxr[MAXN];
struct node{
	int l,r,id;
}que[MAXN];
struct Stack{
    int value[MAXN],numtop;
    Stack(){
        memset(value,0,sizeof(value));
        numtop=0;
    }
    inline void push(int x){value[++numtop]=x;}
    inline void pop(){value[numtop--]=0;}
    inline int top(){return value[numtop];}
    inline int empty(){return (numtop==0?1:0);}
}stack;
inline int read(){
	int date=0,w=1;char c=0;
	while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
	while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
	return date*w;
}
bool cmp1(const node &x,const node &y){
	return x.l<y.l;
}
bool cmp2(const node &x,const node &y){
	if(x.r==y.r)return x.l<y.l;
	return x.r<y.r;
}
void step(){
    for(int i=1;(1<<i)<=n;i++)
    for(int j=1;j+(1<<i)-1<=n;j++){
    	if(val[f[j][i-1]]>val[f[j+(1<<(i-1))][i-1]])f[j][i]=f[j+(1<<(i-1))][i-1];
    	else f[j][i]=f[j][i-1];
    }
}
int query(int l,int r){
    int k=0;
    while((1<<(k+1))<=r-l+1)k++;
	if(val[f[l][k]]>val[f[r-(1<<k)+1][k]])return f[r-(1<<k)+1][k];
    return f[l][k];
}
void work(){
    int left=1,right=0;
	long long s=0;
	for(int i=1;i<=m;i++){
		while(right<que[i].r)
		{
			right++;
			int pos=query(left,right);
			s+=(long long)val[pos]*(pos-left+1)+maxl[right]-maxl[pos];
		}
		while(que[i].l>left)
		{
			int pos=query(left,right);
			s-=(long long)val[pos]*(right-pos+1)+maxr[left]-maxr[pos];
			left++;
		}
		while(right>que[i].r)
		{
			int pos=query(left,right);
			s-=(long long)val[pos]*(pos-left+1)+maxl[right]-maxl[pos];
			right--;
		}
		while(que[i].l<left)
		{
			left--;
		    int pos=query(left,right);
		    s+=(long long)val[pos]*(right-pos+1)+maxr[left]-maxr[pos];
		}
		ans[que[i].id]=s;
	}
	for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
}
void init(){
	int nowi=1,nowj=0,d,x;
    n=read();m=read();
    for(int i=1;i<=n;i++){
        val[i]=read();
        f[i][0]=i;
    }
    step();
	for(int i=1;i<=m;i++){
		que[i].l=read();que[i].r=read();
		que[i].id=i;
	}
	x=sqrt(n);
	sort(que+1,que+m+1,cmp1);
	while(nowi<=m){
		nowj++;
		d=nowi;
		while(que[nowi].l<nowj*x&&nowi<=m)nowi++;
		sort(que+d,que+nowi,cmp2);
		if(nowj==x){
			sort(que+d,que+m+1,cmp2);
			break;
		}
	}
	for(int i=1;i<=n;i++){
	    for(;!stack.empty()&&val[i]<val[stack.top()];stack.pop())next[stack.top()]=i;
	    front[i]=stack.top();
	    stack.push(i);
	}
	for(;!stack.empty();stack.pop())next[stack.top()]=n+1;
	for(int i=1;i<=n;i++)maxl[i]=maxl[front[i]]+(long long)val[i]*(i-front[i]);
	for(int i=n;i>=1;i--)maxr[i]=maxr[next[i]]+(long long)val[i]*(next[i]-i);
}
int main(){
    init();
    work();
	return 0;
}

然后就是莫队了。。。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值