根号分治
思想概述
根号分治,是应对序列问题的方法。对于一个序列问题,设置阀值\(S\),将数分为大于和小于两类,分类处理,达到优化复杂度的目的。\(S\)的大小具体分析。
[CCO2021] Swap Swap Sort
题目描述
你有一个长度为 \(n\) 的序列,每项都是不超过 \(k\) 的正整数。
你的朋友发明了一个排序算法,可以根据一个 \(1 \sim k\) 的排列对序列进行排序,排序后序列中任意两个不相等的数的相对位置与排列中的相对位置相同。他的算法只使用了邻项交换的操作,且总是保证操作次数最少。为了方便描述,他将这个 \(1 \sim k\) 的排列称为目标排列。
例如,序列为 \([1, 4, 2, 1, 2]\),目标排列为 \([4, 1, 2, 3]\),排序后为 \([4, 1, 1, 2, 2]\)。
你对你朋友的排序算法在目标排列不同时执行 swap 的次数很感兴趣。为了研究其中的规律,你一开始将目标排列设置为 \(1 \sim k\),并以此进行 \(q\) 次操作,每次操作交换目标排列中相邻的两个数的位置。每次交换后,你想知道如果用他的排序算法对原序列进行排序会执行 swap 的次数。
输入格式
第一行,三个整数 \(n, k, q\)。
第二行,\(n\) 个正整数 \(a_1, a_2, \cdots, a_n\),表示原序列。
接下来 \(q\) 行,每行一个正整数 \(b\),表示交换目标排列中的第 \(b\) 和第 \(b + 1\) 个数。
输出格式
对于每次询问,输出一个整数,表示所求的值。
样例 #1
样例输入 #1
5 4 3
1 4 2 1 2
3
2
1
样例输出 #1
4
2
2
对于 \(100\%\) 的数据,\(1 \leq n, k \leq 10^5\),\(1 \leq q \leq 10^6\),\(1 \leq a_i \leq k\),\(1 \leq b < k\)。
题解
首先,考虑目标排列是\(1\sim k\)的初始情况。容易证明:对于一次交换,能且仅能消除一个逆序对,所以最初需要的操作次数就是逆序对个数。
接着考虑将两个数\(x,y\)互换对答案造成的影响。
可以这样等效考虑,在目标情况中对两个数互换,等价于在排序后的序列中将\(x,y\)所在部分交换位置(因为\(x,y\)相邻)。进一步的,我们可以看作\(x'=y,y'=x\),目标序列不变,在原序列中把所有的\(x,y\)交换位置,再求逆序对是一样的。
所以因为\(x,y\)相邻,交换他们的位置只能影响这两个数的相互贡献。设在原序列中,有\(h(x,y)\)个数对\((i,j)\),满足\(i<j,a_i=x,a_j=y\),\(h'(x,y)\)个数对\((i,j)\),满足\(i>j,a_i=x,a_j=y\),则有\(h'(x,y)+h(x,y)=cnt_x\times cnt_y\)。
而交换两数位置,只会有:\(Ans=Ans+h'(x',y')-h(x',y')=Ans+cnt_{x'}\times cnt_{y'}-2h(x',y')\)问题转变为求\(h(x',y')\)。
因为\(h(x',y')\)乍一看不咋好求,考虑根号分治。
设阀值为\(S\),对于\(cnt_x\ge S\),可以\(O(n)\)地预处理出\(x\)与其他所有数之间的答案,这样的数不会超过\(\frac{n}{S}\),故空间复杂度\(O(\frac{n^2}{S})\),时间复杂度\(O(\frac{n^2}{S})\)。
对于一组询问\((x,y)\),\(cnt_x< S,cnt_y<S\),则可以开vector
存储每个数出现的位置,双指针扫描一遍,复杂度\(O(qS)\)
总复杂度\(O(\frac{n^2}{S}+qS)\),根据均值不等式,\(\frac{n^2}{S}=qS\)时取得最小值,解得\(S=\frac{n}{\sqrt q}=100\),总复杂度\(O(n\sqrt q)\)。
不过在处理第一种情况的时候,由于空间复杂度为\(O(n\sqrt q)\),吃不消,可以离线下来,单独处理关于每个数的询问,空间复杂度降至\(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();
}
标签:cnt,入门,int,复杂度,分治,leq,序列,排序,根号
From: https://www.cnblogs.com/oierpyt/p/17002694.html