P5677 [GZOI2017] 配对统计(线段树爆改莫队)

[GZOI2017] 配对统计 - 洛谷

贵州的省选,线段树代码太长了,直接莫队启动。

时间复杂度O(n sqrt(n))

加了个奇偶优化。

先按块排左指针。

同一块内右指针按照块的编号奇偶性决定升序或者降序。

核心思路

拓展左右指针

或者缩减

cnt[i]表示当前所在区间中i可以与多少个匹配。

如果拓展到的指针已经包含了配对的左或者右则ans++(配对数增加

缩减同理。

AC代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e5+9;

int n,m;
pair<int,int> s[314514];
int bl[N];
struct qu{
	int l,r,id;
}q[N];
bool cmpq(qu a,qu b){
	if(bl[a.l] != bl[b.l])return bl[a.l] < bl[b.l];
	else return (bl[a.l]&1?a.r > b.r:a.r < b.r);
}
int L[N],R[N];
int cnt[N];
int res;
signed main(){
	ios::sync_with_stdio(0);
	cin>>n>>m;
	int sz = sqrt(n);
	for(int i = 1;i <= n;i++){
		int x;
		cin>>x;
		s[i] = {x,i};
		bl[i] = (i-1)/sz+1;
	}
	sort(s+1,s+1+n);
	for(int i = 1;i <= m;i++){
		int l,r;
		cin>>l>>r;
		q[i].l = l,q[i].r = r,q[i].id = i;
	}
	sort(q+1,q+1+m,cmpq);
	s[0].second = 0;
	s[n+1].second = n+1;
	for (register int i = 1;i <= n;i ++){
        L[s[i].second] = s[i - 1].second;
        R[s[i].second] = s[i + 1].second;
        if(s[i].first - s[i - 1].first > s[i + 1].first - s[i].first && s[i + 1].second <= n) L[s[i].second] = 0;
        if(s[i].first - s[i - 1].first < s[i + 1].first - s[i].first && s[i - 1].second) R[s[i].second] = n + 1;
    }
	int l = q[1].l;
	int r = q[1].l-1;

	int ans = 0;
	for(int i = 1;i <= m;i++){
		//cout<<q[i].l<<" "<<q[i].r<<endl;
		while(l > q[i].l){
			l--;
			ans+=cnt[l];
			cnt[L[l]]++;
			cnt[R[l]]++;
			if(l <= L[l]&&L[l] <= r)ans++;
			if(l <= R[l]&&R[l] <= r)ans++;
		}
		while(r < q[i].r){
			r++;
			ans+=cnt[r];
			cnt[L[r]]++;
			cnt[R[r]]++;
			if(l <= L[r]&&L[r]<=r)ans++;//已经在区间中存在,不可能拓展时访问到
			if(l <= R[r]&&R[r]<=r)ans++;
		}
		while(l < q[i].l){
			ans-=cnt[l];
			cnt[L[l]]--;
			cnt[R[l]]--;
			if(l <= L[l]&&L[l] <= r)ans--;
			if(l <= R[l]&&R[l] <= r)ans--;
			l++;
		}
		while(r > q[i].r){
			ans-=cnt[r];
			cnt[L[r]]--;
			cnt[R[r]]--;
			if(l <= L[r]&&L[r]<=r)ans--;//已经在区间中存在,不可能拓展时访问到
			if(l <= R[r]&&R[r]<=r)ans--;
			r--;
		}
		
		
		res += q[i].id*ans;
	}
	cout<<res;
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值