【洛谷】P5677 [GZOI2017]配对统计(离线+树状数组)

题意

给定 \(n\) 个正整数,对于一组匹配 \((x,y)\),如果对于任意的 \(i=1,2,⋯,n\),满足 \(|a_x-a_y| \leq |a_x-a_i|(i \ne x)\)。那么就称 \((x,y)\) 为一组好的匹配。

给出 \(m\) 询问,每次询问区间 \([l,r]\) 中好的匹配的数量。

记第 \(i\) 次询问的答案为 \(ans_i\),那么最终只需输出 \(\sum_{i=1}^{m} ans_i*i\)

思路

首先观察题目中对于“好的匹配”的定义,可以发现。对于一个数 \(a_x\) ,能和它组成好的匹配的数 \(a_y\) 一定满足 \(|a_x-a_y|\) 的值最小,也就是 \(a_y\) 是与 \(a_x\) 相差最小的数。于是就可以想到排序,将原数组排序后,对于任意的 \(i=2,3,⋯,i-2,i-1\)。能和 \(a_i\) 组成好的匹配的数一定在 \(a_{i-1}\)\(a_{i+1}\) 中(\(a[1]\)\(a[n]\) 需要特判) 。于是就可以在 \(O(n \log n)\) 的时间内求出所有的好的匹配。

然而对于题目中的查询操作。有一个很 navie 的想法:用树状数组 \(c[x]\) 维护区间 \([1,x]\) 内的配对数量,对于每一个 \((x,y)\)(设 \(x<y\) 的),直接 $ add(y,1) $。在查询的时候输出 \(query(r)-query(l-1)\)

可惜这样的想法是错误的,因为在相减的时候只减去了 \(x<l,y<l\) 时的不合法配对,并没有减去 \(x<l,y>l\) 时的不合法配对。

注意到本题中并未要求支持修改操作,于是可以考虑离线做法

上述错误做法出现的问题是并未减去 \(x<l,y>l\) 时的不合法配对。如果将 \(add(y,1)\) 改成 \(add(x,1)\),可以发现当 \(r=n\) 时,\(query(r)-query(l-1)\) 得到的答案就是正确的了,因为此时的 \(query(l-1)\) 中只限制了 \(x<l\),所以 \(x<l,y \geq l\) 的不合法配对也被计算在其中。也就可以在被减去了。

但是一旦当 \(r < n\) 时,这样的做法又是错误的,因为此时 \(query(r)\) 中的配对的 \(y\) 可以比 \(r\) 还大。相减时就无法减去这些不合法匹配。

那如果在树状数组中不记录这些不合法匹配呢?

于是可以想到先将配对按照 \(y\) 的大小进行排序。再将所有的询问离线,先记录下来,再按照右端点 \(r\) 的大小进行排序。在求解每一次询问时,都只把 \(y \leq r\) 的配对记录到树状数组中。这样就避免了 \(r >y\) 的不合法匹配。

关于记录到树状数组中的信息,因为我们想要的是所有满足 \(x \geq l\) 的匹配数量,所以可以直接在树状数组中记录 \(n-x\),这样就可以 \(query(n-l)\) 得到所有匹配的数量。

最后,别忘了开 long long

code:

#include<cstdio>
#include<algorithm>
using namespace std;
#define LL long long
const int N=3e5+10;
const int M=3e5+10;
int n,m,c[N];
LL ans;
struct node{
	int val,id;
	bool operator <(const node &t)const{
		return val<t.val;
	}
}a[N];
struct match{
	int l,r;
	bool operator <(const match &t)const{
	    if(r!=t.r)return r<t.r;
	    return l<t.l;
	}
}p[N<<1];//除了最大和最小的数,极端情况下每个数都能有两个好的匹配,所以数组大小要*2 
int tot=0;
struct quest{
	int l,r,id;
	bool operator <(const quest &t)const{
	    if(r!=t.r)return r<t.r;
	    return l<t.l;
	}
}q[M];
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}
int lowbit(int x){return x&(-x);}
void updata(int x,int k)
{
	for(int i=x;i<=n;i+=lowbit(i)) c[i]+=k;
}
int query(int x)
{
	int res=0;
	for(int i=x;i;i-=lowbit(i)) res+=c[i];
	return res;
}
void add(int a,int b)
{
	p[++tot].l=min(a,b);
	p[tot].r=max(a,b);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i].val),a[i].id=i;
    sort(a+1,a+n+1);
    for(int i=2;i<n;i++)
    {
    	int res1=a[i].val-a[i-1].val,res2=a[i+1].val-a[i].val;
    	if(res1<res2) add(a[i].id,a[i-1].id);
    	else if(res1>res2) add(a[i].id,a[i+1].id);
    	else add(a[i].id,a[i-1].id),add(a[i].id,a[i+1].id);//当两边都相等时当然就都是好的匹配 
	}
	add(a[1].id,a[2].id);
	add(a[n-1].id,a[n].id);//特判 
	sort(p+1,p+tot+1);
	for(int i=1;i<=m;i++) scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;//别忘了记录是第i个询问的答案还要*i 
	sort(q+1,q+m+1);
	int now=1;
	for(int i=1;i<=m;i++)
	{
		while(now<=tot&&p[now].r<=q[i].r) updata(n-p[now++].l,1);
		ans+=(LL)q[i].id*query(n-q[i].l);
	}
	printf("%lld\n",ans);
	return 0;
}
posted @ 2021-06-25 09:10  曙诚  阅读(111)  评论(0)    收藏  举报