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

题意

给定 n 个正整数,对于一组匹配 (x,y),如果对于任意的 i=1,2,,n,满足 |axay||axai|(ix)。那么就称 (x,y) 为一组好的匹配。

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

记第 i 次询问的答案为 ansi,那么最终只需输出 i=1mansii

思路

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

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

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

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

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

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

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

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

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

最后,别忘了开 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 @   曙诚  阅读(83)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示