[AH2017/HNOI2017]影魔

题目

好题啊

先把两种贡献翻译成人话

对于\((i,j)\),如果\(a_i,a_j\)恰好是\([i,j]\)之间的最大值和次大值,那么\((i,j)\)产生\(p1\)的贡献

否则对于\((i,j)\)\(a_i\)是最大值或者\(a_j\)是最大值,那么就产生\(p2\)的贡献

哎这个一个最大值一个严格次大值好像有点眼熟啊

这不bzoj原题

根据那道题我们发现的性质,能产生\(p1\)贡献的点对不会超过\(2n\)个,于是我们用单调栈将这些点对都预处理出来,之后树状数组+扫描线就可以知道每个询问有多少个\(p1\)的贡献

现在求出\(p2\)的贡献就好了

我们考虑一个元素往左右两边扩展,扩展到比它大的元素就停止,这些扫到的点显然都会产生贡献,但是这个点对产生的贡献是\(p1\)还是\(p2\)就不好算了

但是我们都已经算出来\(p1\)的点对数量了,只要拿总数量减一下就是\(p2\)点对的数量了

显然每一个位置往左往右扩展到哪里单调栈已经帮我们求好了,所以现在的问题变成了求每一个询问区间内,每一个位置在区间内往左往右扩展的总长度与之和

考虑把这个扩展的总长度变成一个区间加法,于是我们可以把每一个元素的扩展搞成一个三元组\((l,r,k)\),扩展到\([l,r]\),扩展的位置是\(k\)

我们把每个询问拆成两个,之后将这些区间按照\(k\)来排序,线段树+扫描线就可以了

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lowbit(x) ((x)&(-x))
#define LL long long
#define re register
#define maxn 200005
inline int read() {
	char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
struct Seg{int x,y,k;}t[maxn<<1];
struct node{int x,y;}p[maxn<<1];
struct Ask{int l,r,rk;}q[maxn];
struct _Ask{int x,rk,o,l,r;}_q[maxn<<1];
int n,m,p1,p2,top,tot;
int l[maxn],r[maxn],a[maxn],st[maxn],c[maxn];
int ls[maxn<<2],rs[maxn<<2],tag[maxn<<2];LL d[maxn<<2];
int Ans1[maxn];LL Ans2[maxn];
inline int cxp(_Ask A,_Ask B) {return A.x<B.x;}
inline int cmp(Ask A,Ask B) {return A.r<B.r;}
inline int cop(node A,node B) {return A.y<B.y;}
inline void add(int x,int val) {for(re int i=x;i<=n;i+=lowbit(i)) c[i]+=val;}
inline int ask(int x) {int cnt=0;for(re int i=x;i;i-=lowbit(i)) cnt+=c[i];return cnt;}
void build(int x,int y,int i) {
	ls[i]=x,rs[i]=y;
	if(x==y) return;
	int mid=x+y>>1;
	build(x,mid,i<<1),build(mid+1,y,i<<1|1);
}
inline void pushdown(int i) {
	if(!tag[i]) return;
	tag[i<<1]+=tag[i],tag[i<<1|1]+=tag[i];
	d[i<<1]+=tag[i]*(rs[i<<1]-ls[i<<1]+1);
	d[i<<1|1]+=tag[i]*(rs[i<<1|1]-ls[i<<1|1]+1);
	tag[i]=0;
}
void change(int x,int y,int i) {
	if(x<=ls[i]&&y>=rs[i]) {
		tag[i]++;
		d[i]+=rs[i]-ls[i]+1;
		return;
	}
	pushdown(i);
	int mid=ls[i]+rs[i]>>1;
	if(y<=mid) change(x,y,i<<1);
		else if(x>mid) change(x,y,i<<1|1);
			else change(x,y,i<<1|1),change(x,y,i<<1);
	d[i]=d[i<<1|1]+d[i<<1];
}
int query(int x,int y,int i) {
	if(x<=ls[i]&&y>=rs[i]) return d[i];
	pushdown(i);
	int mid=ls[i]+rs[i]>>1;
	if(y<=mid) return query(x,y,i<<1);
	if(x>mid) return query(x,y,i<<1|1);
	return query(x,y,i<<1|1)+query(x,y,i<<1);
}
signed main() {
	n=read(),m=read();p1=read(),p2=read();
	for(re int i=1;i<=n;i++) a[i]=read();
	for(re int i=1;i<=n;i++) {
		while(top&&a[st[top]]<a[i]) r[st[top--]]=i;
		st[++top]=i;
	}
	while(top) r[st[top--]]=n+1;
	for(re int i=n;i;--i) {
		while(top&&a[st[top]]<a[i]) l[st[top--]]=i;
		st[++top]=i;
	}
	while(top) l[st[top--]]=0;
	for(re int i=1;i<=n;i++) {
		if(l[i]) p[++tot]=(node){l[i],i};
		if(r[i]!=n+1) p[++tot]=(node){i,r[i]};
	}
	for(re int i=1;i<=m;i++) q[i].l=read(),q[i].r=read(),q[i].rk=i;
	std::sort(p+1,p+tot+1,cop),std::sort(q+1,q+m+1,cmp);
	top=1;int now=1;
	for(re int i=1;i<=n;i++) {
		while(now<=tot&&p[now].y<=i) add(p[now].x,1),now++;
		while(top<=m&&q[top].r<=i) 
			Ans1[q[top].rk]=ask(q[top].r)-ask(q[top].l-1),top++;
	}
	tot=0;now=0;build(1,n,1);
	for(re int i=1;i<=n;i++) {
		if(l[i]+1<=i-1) t[++tot]=(Seg){l[i]+1,i-1,i};
		if(i+1<=r[i]-1)	t[++tot]=(Seg){i+1,r[i]-1,i};
	}
	for(re int i=1;i<=m;i++) 
		_q[++now]=(_Ask){q[i].l-1,q[i].rk,-1,q[i].l,q[i].r},_q[++now]=(_Ask){q[i].r,q[i].rk,1,q[i].l,q[i].r};
	std::sort(_q+1,_q+now+1,cxp);
	int tmp=1,cnt=1;
	for(re int i=0;i<=n;i++) {
		while(tmp<=tot&&t[tmp].k<=i) change(t[tmp].x,t[tmp].y,1),tmp++;
		while(cnt<=now&&_q[cnt].x<=i) Ans2[_q[cnt].rk]+=_q[cnt].o*query(_q[cnt].l,_q[cnt].r,1),cnt++;
	}
	for(re int i=1;i<=m;i++) 
		printf("%lld\n",(LL)p1*Ans1[i]+(LL)p2*(Ans2[i]-Ans1[i]));
	return 0;
}
posted @ 2019-02-28 20:51  asuldb  阅读(229)  评论(0编辑  收藏  举报