题解[GZOI2017]配对统计

题目描述

Link

给定(n)个数(a_1,⋯ ,a_n)

对于一组配对((x,y)),若对于所有的(i=1,2,⋯ ,n),满足(∣ax−ay∣)(∣ax−ai∣)((i≠x)),则称((x,y))为一组好的配对.

给出若干询问,每次询问区间([l,r])中含有多少组好的配对。

即,取(x,y(l≤x,y≤r且x≠y)) ,问有多少组((x,y))是好的配对。

思路

这道题只有查询操作,没有修改和强制在线,所以我们可以离线做。

要用到的方法:类似权值树状数组的,排序,还有一点莫队的思想

给原数组由小到大排序,询问数组左端点由大到小排序,方便我们统计答案时可以一直往左找。

预处理出所有好对的左端点和右端点。

询问一个区间([l,r])时,把所有左端点大于等于(l)的好对的右端点加入树状数组,统计右端点(le r)的数量就好了

由于询问的左端点一直向左扩展,所以加过的右端点不需要删。

Code

#include<bits/stdc++.h>
#define N (300010)
#define INF (2e9)
#define lb(x) (x&(-x))
#define ll long long
using namespace std;
struct Q{int l,r,v;}q[N];//询问数组
struct NUM{int v,id;}a[N];//值数组
struct P{int l,r;}p[N];//好对数组
int n,m,cnt;
ll ans,c[N];//树状数组
inline int read(){
	int w=0;
	char ch=getchar();
	while(ch>'9'||ch<'0') ch=getchar();
	while(ch>='0'&&ch<='9'){
		w=(w<<3)+(w<<1)+(ch^48);
		ch=getchar();
	}
	return w;
}
inline bool cmp2(P a,P b){return a.l<b.l;}
inline bool cmp1(NUM a,NUM b){return a.v<b.v;}
inline bool cmp(Q a,Q b){return a.l>b.l;}
inline void add(int x,ll v){for(int i=x;i<=n;i+=lb(i)) c[i]+=v;}
inline ll sum(int x){
	ll res=0;
	while(x){
		res+=c[x];
		x-=lb(x);
	}
	return res;
}
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++) a[i].v=read(),a[i].id=i;
	for(int i=1;i<=m;i++){
		q[i].l=read(),q[i].r=read();
		q[i].v=i;
	}
	sort(q+1,q+1+m,cmp);
	sort(a+1,a+1+n,cmp1);
	a[0].v=-INF,a[n+1].v=INF;
	for(int i=1;i<=n;i++){
		if(a[i].v-a[i-1].v<=a[i+1].v-a[i].v) p[++cnt]=(P){min(a[i-1].id,a[i].id),max(a[i-1].id,a[i].id)};
		if(a[i].v-a[i-1].v>=a[i+1].v-a[i].v) p[++cnt]=(P){min(a[i].id,a[i+1].id),max(a[i].id,a[i+1].id)};
	}
  //预处理好对
	sort(p+1,p+1+cnt,cmp2);
	for(int i=1;i<=m;i++){
		while(p[cnt].l>=q[i].l) add(p[cnt].r,1),cnt--;//拓展左端点
		ll now=sum(q[i].r);
		ans+=q[i].v*now;
	}
	printf("%lld
",ans);
	return 0;
}

完结撒花❀

原文地址:https://www.cnblogs.com/xxbbkk/p/14360687.html