2141: 排队

这道题我以前貌似写过,然而写的是分块+树状数组。呸。线段树。(讲道理当时我只会线段树)

显然这题可以等效为求一段区间内比自己大的数的个数。

对分出每个块建一个树状数组,然后零散的点直接扫一遍,块内的点用树状数组统计。

这个算法实在是太低了。这里介绍一个O(n*sqrt(n))的做法。

分块。先按下标分块,在每个块内统计每个值出现的次数,并按权值分块,统计块内数的个数。

然后对按下标分的块进行前缀和。

然后发现自己是询问O(sqrt(n))修改O(sqrt(n))的。

讲道理梁大orz跟我讲的是修改O(sqrt(n))询问O(1)的,然而我没听懂,于是就YY了上面那种算法。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<vector>
#include<algorithm>
#include<set>
#include<map>
#include<cmath>
using namespace std;
#define rep(i,j,k) for(i=j;i<=k;++i)
#define per(i,j,k) for(i=j;i>=k;--i)
#define G getchar()
#define LL long long
#define pii pair<int,int>
#define mkp make_pair
#define X first
#define Y second
const int N=20005,NN=200;
int n,m,a[N],sa[N],b[N],ans;
int s,t,mo[N],g[NN][N],f[NN][NN];
void read(int &x){
	char ch=G;
	while(ch<48||ch>57)ch=G;
	for(x=0;ch>47&&ch<58;ch=G)x=x*10+ch-48;
}
bool cmp(int x,int y){
	return b[x]<b[y];
}
void merge(int l,int r){
	if(l==r)return;
	int mid=l+r>>1,i,j;
	merge(l,mid);merge(mid+1,r);
	sort(b+l,b+mid+1);sort(b+mid+1,b+r+1);
	for(i=l,j=mid+1;i<=mid;++i){
		while(j<=r&&b[j]<b[i])++j;
		ans+=j-mid-1;
	}
}
int query(int l,int r,int x){
	int rtn=0,y;
	if(r-l<s){
		for(;l<=r;++l)rtn+=(a[l]<x)-(a[l]>x);
		return rtn;
	}
	for(;l%s!=1;++l)rtn+=(a[l]<x)-(a[l]>x);
	for(;r%s;--r)rtn+=(a[r]<x)-(a[r]>x);
	if(l>r)return rtn;
	l=mo[l],r=mo[r];
	for(y=x-1;y%s;--y)rtn+=g[r][y]-g[l-1][y];
	for(y=mo[y];y;--y)rtn+=f[r][y]-f[l-1][y];
	for(y=x+1;y<=n&&y%s!=1;++y)rtn-=g[r][y]-g[l-1][y];
	for(y=mo[y];y<=t;++y)rtn-=f[r][y]-f[l-1][y];
	return rtn;
}
void add(int x,int y,int z){
	int i,j;
	rep(i,x,t)
		f[i][mo[y]]+=z,g[i][y]+=z;
}
int main(){
//	freopen("r.in","r",stdin);
//	freopen("w.out","w",stdout);
	int i,j,l,r;
	read(n);
	rep(i,1,n)read(b[sa[i]=i]);
	sort(sa+1,sa+n+1,cmp);
	j=0;
	rep(i,1,n)a[sa[i]]=j+=b[sa[i]]!=b[sa[i-1]];
	rep(i,1,n)b[i]=a[i];
	merge(1,n);printf("%d\n",ans);
	s=sqrt(n);
	rep(i,1,n)mo[i]=t+=i%s==1;mo[i]=t+1;
	rep(i,1,n)++f[mo[i]][mo[a[i]]],++g[mo[i]][a[i]];
	rep(i,2,t){
		rep(j,1,t)f[i][j]+=f[i-1][j];
		rep(j,1,n)g[i][j]+=g[i-1][j];
	}
	for(read(m);m--;){
		read(l);read(r);
		if(l==r){printf("%d\n",ans);continue;}
		if(l>r)swap(l,r);
		ans+=query(l+1,r-1,a[r])-query(l+1,r-1,a[l]);
		if(a[l]<a[r])++ans;
		if(a[l]>a[r])--ans;
		printf("%d\n",ans);
		add(mo[l],a[l],-1);add(mo[l],a[r],1);
		add(mo[r],a[r],-1);add(mo[r],a[l],1);
		swap(a[l],a[r]);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值