根号分治入门

根号分治

思想概述

根号分治,是应对序列问题的方法。对于一个序列问题,设置阀值S,将数分为大于和小于两类,分类处理,达到优化复杂度的目的。S的大小具体分析。

[CCO2021] Swap Swap Sort

题目描述

你有一个长度为 n 的序列,每项都是不超过 k 的正整数。

你的朋友发明了一个排序算法,可以根据一个 1k 的排列对序列进行排序,排序后序列中任意两个不相等的数的相对位置与排列中的相对位置相同。他的算法只使用了邻项交换的操作,且总是保证操作次数最少。为了方便描述,他将这个 1k 的排列称为目标排列。

例如,序列为 [1,4,2,1,2],目标排列为 [4,1,2,3],排序后为 [4,1,1,2,2]

你对你朋友的排序算法在目标排列不同时执行 swap 的次数很感兴趣。为了研究其中的规律,你一开始将目标排列设置为 1k,并以此进行 q 次操作,每次操作交换目标排列中相邻的两个数的位置。每次交换后,你想知道如果用他的排序算法对原序列进行排序会执行 swap 的次数。

输入格式

第一行,三个整数 n,k,q

第二行,n 个正整数 a1,a2,,an,表示原序列。

接下来 q 行,每行一个正整数 b,表示交换目标排列中的第 b 和第 b+1 个数。

输出格式

对于每次询问,输出一个整数,表示所求的值。

样例 #1

样例输入 #1

5 4 3
1 4 2 1 2
3
2
1

样例输出 #1

4
2
2

对于 100% 的数据,1n,k1051q1061aik1b<k

题解

首先,考虑目标排列是1k的初始情况。容易证明:对于一次交换,能且仅能消除一个逆序对,所以最初需要的操作次数就是逆序对个数。

接着考虑将两个数x,y互换对答案造成的影响。

可以这样等效考虑,在目标情况中对两个数互换,等价于在排序后的序列中将x,y所在部分交换位置(因为x,y相邻)。进一步的,我们可以看作x=y,y=x,目标序列不变,在原序列中把所有的x,y交换位置,再求逆序对是一样的。

所以因为x,y相邻,交换他们的位置只能影响这两个数的相互贡献。设在原序列中,有h(x,y)个数对(i,j),满足i<j,ai=x,aj=yh(x,y)个数对(i,j),满足i>j,ai=x,aj=y,则有h(x,y)+h(x,y)=cntx×cnty

而交换两数位置,只会有:Ans=Ans+h(x,y)h(x,y)=Ans+cntx×cnty2h(x,y)问题转变为求h(x,y)

因为h(x,y)乍一看不咋好求,考虑根号分治。

设阀值为S,对于cntxS,可以O(n)地预处理出x与其他所有数之间的答案,这样的数不会超过nS,故空间复杂度O(n2S),时间复杂度O(n2S)

对于一组询问(x,y)cntx<S,cnty<S,则可以开vector存储每个数出现的位置,双指针扫描一遍,复杂度O(qS)

总复杂度O(n2S+qS),根据均值不等式,n2S=qS时取得最小值,解得S=nq=100,总复杂度O(nq)

不过在处理第一种情况的时候,由于空间复杂度为O(nq),吃不消,可以离线下来,单独处理关于每个数的询问,空间复杂度降至O(n)

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
using namespace std;
#define N 100050
#define Q 1000500
#define int long long
struct node{
	int x,y,id,tag;
};
vector<node>t[N]; 
vector<int>seat[N];
int n,m,k,q,s=100,c[N],ans,b[N],a[N],cnt1[N],cnt2[N],c1[N],Ans[Q],f[N],vis[N];
#define lowbit(x) x&-x
void add(int x,int k1){
	for(int i=x;i<=k;i+=lowbit(i))c1[i]+=k1;
}
int ask(int x){
	int ans=0;
	for(int i=x;i;i-=lowbit(i))ans+=c1[i];
	return ans;
}
int get(int x,int y){//h(x,y) 
	int ans=0;
	int l=0,r=0,len1=seat[x].size(),len2=seat[y].size();
	for(r=0;r<len2;r++){
		while(seat[x][l]<seat[y][r]&&l<len1)l++;
		ans+=l;
	} 
	return (c[x]*c[y]-2*ans);
}
void init(){
	cin>>n>>k>>q;
	s=n/sqrt(q)+3;
	for(int i=1;i<=k;i++)b[i]=i;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++)seat[a[i]].push_back(i);
	for(int i=1;i<=n;i++)c[a[i]]++;
	for(int i=n;i;--i){
		ans+=ask(a[i]-1);
		add(a[i],1);
	}
	for(int i=1;i<=q;i++){
		int xb;
		cin>>xb;
		swap(b[xb],b[xb+1]);
		int x=b[xb],y=b[xb+1];
		if(c[x]<s&&c[y]<s){
			if(c[x]==0||c[y]==0)Ans[i]=0;
			else Ans[i]=get(x,y);
		}
		else {
			if(c[x]<s)t[y].push_back((node){y,x,i,-1});
			else t[x].push_back((node){x,y,i,1});
		}
	}
	for(int i=1;i<=n;i++){//h(a[i],a[j]) 
		if(c[a[i]]<s||vis[a[i]])continue;
		memset(f,0,sizeof f);
		int cnt1=0;
		for(int j=1;j<=n;j++){
			if(a[j]==a[i])cnt1++;
			else{
				f[a[j]]+=cnt1;
			}
		}
		vis[a[i]]=1;
		int len1=t[a[i]].size();
		for(int j=0;j<len1;j++){
			Ans[t[a[i]][j].id]=t[a[i]][j].tag*(c[t[a[i]][j].x]*c[t[a[i]][j].y]-2*f[t[a[i]][j].y]);
		}
	}
	for(int i=1;i<=q;i++){
		ans+=Ans[i];
		cout<<ans<<endl;
	}
}
signed main(){
//	freopen("data.in","r",stdin);
//	freopen("data.ans","w",stdout);
	ios::sync_with_stdio(false);
	init();
}
posted @   spdarkle  阅读(115)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示