【学习笔记】快乐分块
快乐分块
题外话
为什么分块算在树专题里(
引入
首先肯定是要统计出原来有多少个逆序对的
然后考虑如何统计交换后的答案改变
老师!我会暴力统计!
你退役罢(无慈悲
我直接给出结论(其实是我不会证明)
设交换的位置分别为\(u,v\),操作前逆序对个数为\(ans\)
容易得到:
对于\(i \in (u,v)\)
若\(h_i < h_v\)或\(h_i > h_u\),则ans++
若\(h_i > h_v\)或\(h_i < h_u\),则ans--
然后问题转化为统计\((u,v)\)中有多少个数满足以上四种条件,就可以愉快树套树了
时间复杂度\(O(nlog^2n)\)
可我不会树套树啊(
所以我们考虑对原数组分块
分块是什么
简而言之,分块是对数组进行分组,对于包含在区间内的整个组直接用较低时间复杂度的方法回答询问,剩下的则暴力统计
其中每一个整组叫做“块”
拿之前例题举例
假设没有修改操作
如果询问的\((u,v)\)都是\((1,n)\),显然我们可以对整个数组进行排序,然后二分得到答案
假设我们已经对原数组进行了分块,那么对于每个在区间\((u,v)\)里的整块,我们都可以对其进行排序,然后二分得到答案
对于剩下的非整块元素(一般称作边角),直接暴力统计
假设我们每\(K\)个元素分成一组,那么这么一趟的最坏时间复杂度为:
\(O(K+\frac{n}{K}logK)\)
显然当\(K=\sqrt{n}\)时时间复杂度最优,为\(O(\sqrt{n}+\sqrt{n}log\sqrt{n})\)
感觉十分科学的时间复杂度(
现在,还有修改
我们原本的块是有序的
新交换进来的数,我们直接对新来的数插入排序即可
时间复杂度\(O(K)\)
考虑到询问的复杂度,仍是当\(K=\sqrt{n}\)时时间复杂度最优
分块的优势
当你不会写树套树时骗分
对于树套树这种码量大又不好调的数据结构,我这种人在考场上基本不可能打出来
相比较而说,分块会相对好些很多
况且,对于树套树那巨大的常数,分块的小常数常常能让他跑得比复杂度更优的复杂度更快
还有呢
对于部分树套树难以统计的信息,分块常常能更好地统计
深刻理解请去刷ynOI
例题代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N=20010;
inline void read(int &x) {
x=0;
int f=1;
char ch=getchar();
while(ch<'0'||ch>'9') {
if (ch=='-') {
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
x*=f;
}
int ans;
int a[N];
namespace merge_sort {
int t[N];
inline void merge_sort(int l,int r) {
if (r<=l+1) {
return;
}
int mid=(l+r)>>1;
int p=l,q=mid,cur=l;
merge_sort(l,mid);
merge_sort(mid,r);
while(p<mid||q<r) {
if (q>=r||(p<mid&&a[p]<=a[q])) {
t[cur++]=a[p++];
} else {
t[cur++]=a[q++];
ans+=(long long)mid-p;
}
}
for(int i=l;i<r;i++) {
a[i]=t[i];
}
}
}
int n,m;
int siz;
int h[N];
int id[N],lft[N],rgh[N];
int k[N];
inline void change_ans(int x,int y,int i) {
if (h[i]>h[x]) {
ans++;
}
if (h[i]<h[x]) {
ans--;
}
if (h[i]<h[y]) {
ans++;
}
if (h[i]>h[y]) {
ans--;
}
}
void resort(int x,int y) {
int L=id[x],R=id[y];
int l=lower_bound(k+lft[L],k+rgh[L]+1,h[x])-k;
int r=lower_bound(k+lft[R],k+rgh[R]+1,h[y])-k;
k[l]=h[y];
int tmp=l;
while(tmp>lft[L]&&k[tmp]<k[tmp-1]) {
swap(k[tmp],k[tmp-1]);
tmp--;
}
while(tmp<rgh[L]&&k[tmp]>k[tmp+1]) {
swap(k[tmp],k[tmp+1]);
tmp++;
}
k[r]=h[x];
tmp=r;
while(tmp>lft[R]&&k[tmp]<k[tmp-1]) {
swap(k[tmp],k[tmp-1]);
tmp--;
}
while(tmp<rgh[R]&&k[tmp]>k[tmp+1]) {
swap(k[tmp],k[tmp+1]);
tmp++;
}
swap(h[x],h[y]);
/*
printf("k:");
for(int i=1;i<=n;i++) {
printf("%d ",k[i]);
}
printf("\nh:");
for(int i=1;i<=n;i++) {
printf("%d ",h[i]);
}
*/
}
int main() {
read(n);
siz=sqrt(n);
for(int i=1;i<=n;i++) {
read(h[i]);
k[i]=a[i]=h[i];
id[i]=(i-1)/siz+1;
}
for(int i=1;i<=n;i++) {
if (!lft[id[i]]) {
lft[id[i]]=i;
}
int j=n-i+1;
if (!rgh[id[j]]) {
rgh[id[j]]=j;
}
}
for(int i=1;i<=id[n];i++) {
sort(k+lft[i],k+rgh[i]+1);
}
merge_sort::merge_sort(1,n+1);
printf("%d\n",ans);
read(m);
for(int i=1;i<=m;i++) {
int x,y;
read(x),read(y);
if (x>y) {
swap(x,y);
}
if (h[x]==h[y]) {
printf("%d\n",ans);
continue;
}
if (h[x]<h[y]) {
ans++;
} else {
ans--;
}
if (id[x]==id[y]) {
for(int i=x+1;i<y;i++) {
change_ans(x,y,i);
}
printf("%d\n",ans);
resort(x,y);
continue;
}
for(int i=x+1;i<=rgh[id[x]];i++) {
change_ans(x,y,i);
}
for(int i=lft[id[y]];i<y;i++) {
change_ans(x,y,i);
}
//统计以下:
//有多少个数小于h[x]:-
//有多少个数小于h[y]:+
//有多少个数大于h[x]:+
//有多少个数大于h[y]:-
for(int i=id[x]+1;i<id[y];i++) {
int o1,o2,o3,o4;
o1=o2=o3=o4=0;
if (k[lft[i]]<h[x]) {
o1=lower_bound(k+lft[i],k+rgh[i]+1,h[x])-k;
o1=o1-lft[i];
}//h[x]-
if (k[rgh[i]]>h[x]) {
o2=upper_bound(k+lft[i],k+rgh[i]+1,h[x])-k;
o2=rgh[i]+1-o2;
}//h[x]+
if (k[lft[i]]<h[y]) {
o3=lower_bound(k+lft[i],k+rgh[i]+1,h[y])-k;
o3=o3-lft[i];
}//h[y]+
if (k[rgh[i]]>h[y]) {
o4=upper_bound(k+lft[i],k+rgh[i]+1,h[y])-k;
o4=rgh[i]+1-o4;
}//h[y]-;
//printf("o:%d %d %d %d\n",o1,o2,o3,o4);
ans=ans-o1+o2+o3-o4;
}
printf("%d\n",ans);
resort(x,y);
}
return 0;
}