顺序对题解(分块)

题目描述

你一开始有一个长度为 n n n的升序排列 a a a

q q q次询问,每次询问都会交换两个数 a l a_l al a r a_r ar,并求交换后整个排列顺序对的数量。

本题强制在线。

输入格式

第一行两个整数 n , q n,q n,q

接下来 q q q行,每行两个整数 l , r l,r l,r,表示一次询问。输入是加密的,解密方法如下:

令上次询问的结果为 x x x(如果这是第一次询问,则 x = 0 x=0 x=0),你需要将 l , r l,r l,r分别异或上 x x x,对 n n n取模后再加 1 1 1,两者的较小值为真实的 l l l,较大值为真实的 r r r

操作有后效性,即前面操作到后面仍不还原。

输出格式

一共 q q q,行,每行一个整数表示答案。

样例输入

5 3
1 3
4 2
1 5

样例输出

7
6
7

数据范围

1 ≤ n ≤ 1 0 5 , 1 ≤ q ≤ 1 0 5 1\leq n\leq 10^5,1\leq q\leq 10^5 1n105,1q105


题解

显然, a l a_l al a r a_r ar交换后,受影响的只有区间 [ l + 1 , r − 1 ] [l+1,r-1] [l+1,r1]。对于区间 [ l + 1 , r − 1 ] [l+1,r-1] [l+1,r1],设区间长为 l e n len len,分如下情况考虑

  • 对于 a l a_l al,区间中大于 a l a_l al的与 a l a_l al构成顺序对,交换后构成逆序对,应减去;区间中小于 a l a_l al的与 a l a_l al构成逆序对,交换后构成顺序对,应加上
  • 对于 a r a_r ar,区间中小于 a r a_r ar的与 a r a_r ar构成顺序对,交换后构成逆序对,应减去;区间中大于 a r a_r ar的与 a r a_r ar构成逆序对,交换后构成顺序对,应加上
  • 还要考虑 a l a_l al a r a_r ar交换后两个数对答案的影响。如果 a l < a r a_l<a_r al<ar,则答案减一;如果 a l > a r a_l>a_r al>ar,则答案加一

设区间长为 l e n len len,区间中大于 a l a_l al的数为 p p p,大于 a r a_r ar的数为 q q q,则交换后,区间 [ l + 1 , r − 1 ] [l+1,r-1] [l+1,r1]对答案的影响为 ( l e n − p ) − p + q − ( l e n − q ) (len-p)-p+q-(len-q) (lenp)p+q(lenq)

我们可以用分块来维护序列,用一个数组来存储每一块排序后的序列。对于一次查询,整块的可以二分查询大于 x x x的数,而非整块的可以暴力求出。交换 a l a_l al a r a_r ar可以用插入排序分别修改两个数对应的块。

区间查询的时间复杂度为 O ( n log ⁡ n ) O(\sqrt n\log n) O(n logn),区间修改的时间复杂度为 O ( n ) O(\sqrt n) O(n ),总时间复杂度为 O ( n n log ⁡ n ) O(n\sqrt n\log n) O(nn logn)

code

#include<bits/stdc++.h>
using namespace std;
int n,q,bl,a[100005],b[100005];
long long lst=0,ans=0;
int dd(int x,int l,int r){
	if(l>r) return 0;
	int re=0;
	int vl=(l-1)/bl+1,vr=(r-1)/bl+1;
	if(vl==vr){
		for(int i=l;i<=r;i++){
			if(a[i]>x) ++re;
			else --re;
		}
		return re;
	}
	for(int i=l;i<=vl*bl;i++){
		if(a[i]>x) ++re;
		else --re;
	}
	for(int i=vr*bl-bl+1;i<=r;i++){
		if(a[i]>x) ++re;
		else --re;
	}
	for(int i=vl+1;i<=vr-1;i++){
		int t;
		if(b[i*bl]<x) t=bl;
		else t=upper_bound(b+i*bl-bl+1,b+i*bl+1,x)-b-(i-1)*bl-1;
		re+=bl-t-t;
	}
	return re;
}
void pt(int x,int v1,int v2){
	int w,vl=x*bl-bl+1,vr=min(x*bl,n);
	for(int i=vl;i<=vr;i++){
		if(b[i]==v1){
			w=i;
			b[i]=v2;
			break;
		}
	}
	int p=w;
	while(p-1>=vl&&b[p-1]>b[p]){
		swap(b[p-1],b[p]);
		--p;
	}
	p=w;
	while(p+1<=vr&&b[p+1]<b[p]){
		swap(b[p],b[p+1]);
		++p;
	}
}
int main()
{
	scanf("%d%d",&n,&q);
	bl=sqrt(n);
	for(int i=1;i<=n;i++){
		a[i]=b[i]=i;
	}
	ans=1ll*n*(n-1)/2;
	while(q--){
		int l,r;
		scanf("%d%d",&l,&r);
		l=(l+lst+n-1)%n+1;
		r=(r+lst+n-1)%n+1;
		if(l>r) swap(l,r);
		ans=ans-dd(a[l],l+1,r-1)+dd(a[r],l+1,r-1);
		if(a[l]>a[r]) ++ans;
		else if(a[l]<a[r]) --ans;
		pt((l-1)/bl+1,a[l],a[r]);
		pt((r-1)/bl+1,a[r],a[l]);
		swap(a[l],a[r]);
		printf("%lld\n",ans);
		lst=ans%n;
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值