bzoj3744:Gty的妹子序列 (分块+树状数组)

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3744

题目分析:一看这数据范围:50000,铁定分块+数据结构。然后本人开始YY,先离散化。记录g[i]为每一块内的逆序对个数(每一个g[i]可以用sqrt(n)*log(n)的时间搞定)。然后我们记录f[i][j]为第i块到第j块中的逆序对个数,很明显f[i][j]=g[i]+f[i+1][j]+x,其中x=sigma(每一个在第i块中的数y,第i+1~j块中有多少个数比它小)。于是后面那一段我直接作死上主席树,虽然理论时间复杂度不变,但常数大到飞起。构造每一个f[i][j]的时间为sqrt(n)*log(n)。

接下来对于每一个询问,处理左,右边独立的部分的逆序对(这里我又作死用主席树,屡试不爽),时间sqrt(n)*log(n)。答案再加上中间连续的几块的f[i][j]。再加上左边,中间对右边的影响,于是枚举右边,找左边,中间有几个比它大的,又一个sqrt(n)*log(n)(这里我还是用了主席树)。再考虑中间对左边的影响,又一个sqrt(n)*log(n)……

果不其然TLE,极限数据本地评测25s+,QAQ,感觉暴力都比我快。

上网看了别人大神的题解,n*sqrt(n)预处理出cs[j][i]表示前j个块中有多少小于i的,cb[j][i]表示前j个块中有多少大于i的。然后构造每个f[i][j]的时间就降到了sqrt(n)。询问的时候除了处理左,右边独立的部分的逆序对,以及左右两个部分的相互影响要用树状数组sqrt(n)*log(n)外,其他都是O(1)的。

再跑极限数据,本地评测3s……

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;

const int maxn=51000;
const int maxm=280;
const long double eps=1e-9;

struct data
{
	int val,id,Time;
} a[maxn];
int temp=1;

int g[maxm];
int f[maxm][maxm];

int cs[maxm][maxn];
int cb[maxm][maxn];

int bit[maxn];
int n,m,sn,num;

bool Comp1(data x,data y)
{
	return x.val<y.val;
}

bool Comp2(data x,data y)
{
	return x.Time<y.Time;
}

void Reset_a()
{
	sort(a+1,a+n+1,Comp1);
	a[1].id=1;
	for (int i=2; i<=n; i++)
		a[i].id=(a[i-1].val<a[i].val)? ++temp:temp;
	sort(a+1,a+n+1,Comp2);
}

int Sum(int x)
{
	int s=0;
	while (x)
	{
		s+=bit[x];
		x-=(x&(-x));
	}
	return s;
}

void Add(int x,int v)
{
	while (x<=temp)
	{
		bit[x]+=v;
		x+=(x&(-x));
	}
}

int Work(int L,int R,bool x)
{
	int v=0;
	for (int i=R; i>=L; i--)
		v+=Sum(a[i].id-1),Add(a[i].id,1);
	if (x) for (int i=L; i<=R; i++) Add(a[i].id,-1);
	return v;
}

void Make_g()
{
	sn=(int)floor( sqrt( (long double)n )+eps );
	for (num=1; (num-1)*sn<n; num++)
	{
		int head=(num-1)*sn+1;
		int tail=min(num*sn,n);
		g[num]=Work(head,tail,true);
	}
	num--;
}

void Make_cs()
{
	for (int j=1; j<=num; j++)
	{
		int head=(j-1)*sn+1;
		int tail=min(j*sn,n);
		for (int i=head; i<=tail; i++) cs[j][ a[i].id ]++;
		for (int i=2; i<=temp; i++) cs[j][i]+=cs[j][i-1];
		cs[j][0]=0;
		for (int i=temp; i>=1; i--) cs[j][i]=cs[j][i-1];
	}
	for (int i=1; i<=temp; i++)
		for (int j=2; j<=num; j++)
			cs[j][i]+=cs[j-1][i];
}

void Make_cb()
{
	for (int j=1; j<=num; j++)
	{
		int head=(j-1)*sn+1;
		int tail=min(j*sn,n);
		for (int i=head; i<=tail; i++) cb[j][ a[i].id ]++;
		for (int i=temp-1; i>=1; i--) cb[j][i]+=cb[j][i+1];
		cb[j][temp+1]=0;
		for (int i=1; i<=temp; i++) cb[j][i]=cb[j][i+1];
	}
	for (int i=1; i<=temp; i++)
		for (int j=2; j<=num; j++)
			cb[j][i]+=cb[j-1][i];
}

void Make_f()
{
	for (int r=1; r<=num; r++)
	{
		int tail=min(r*sn,n);
		f[r][r]=g[r];
		for (int l=r-1; l>=1; l--)
		{
			f[l][r]=f[l+1][r]+g[l];
			int nh=(l-1)*sn+1;
			int nt=l*sn;
			for (int i=nh; i<=nt; i++)
				f[l][r]+=(cs[r][ a[i].id ]-cs[l][ a[i].id ]);
		}
	}
}

void Preparation()
{
	Reset_a();
	Make_g();
	Make_cs();
	Make_cb();
	Make_f();
}

int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	
	scanf("%d",&n);
	for (int i=1; i<=n; i++)
		scanf("%d",&a[i].val),a[i].Time=i;
	
	Preparation();
	
	scanf("%d",&m);
	int ans=0;
	for (int i=1; i<=m; i++)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		l^=ans;
		r^=ans;
		
		int A=l;
		while ( A<r && A%sn ) A++;
		if (A==r)
		{
			ans=Work(l,r,true);
			printf("%d\n",ans);
			continue;
		}
		
		ans=Work(l,A,true);
		int b=A/sn+1,c;
		for (c=b; c*sn<=r; c++);
		c--;
		int head=c*sn+1;
		
		if (c>=b)
		{
			ans+=f[b][c];
			for (int i=head; i<=r; i++)
				ans+=(cb[c][ a[i].id ]-cb[b-1][ a[i].id ]);
		}
		
		ans+=Work(head,r,false);
		for (int i=l; i<=A; i++)
		{
			int x=a[i].id;
			ans+=(cs[c][x]-cs[b-1][x]);
			ans+=Sum(x-1);
		}
		for (int i=head; i<=r; i++) Add(a[i].id,-1);
		
		printf("%d\n",ans);
	}
	
	return 0;
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值