C/C++教程

【洛谷P5046】Yuno loves sqrt technology I

本文主要是介绍【洛谷P5046】Yuno loves sqrt technology I,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

题目

题目链接:https://www.luogu.com.cn/problem/P5046
给你一个长为 \(n\) 的排列,\(m\) 次询问,每次查询一个区间的逆序对数,强制在线。
\(n,m\leq 10^5\)。

思路

分块。设块长为 \(B\)。
预处理 \(f[i][j]\) 表示第 \(i\) 块和序列前 \(i\) 位互相产生的逆序对数量。考虑先求第 \(i\) 块和第 \(j\) 位的逆序对数量,然后求一下前缀和。
设第 \(i\) 块是区间 \([l,r]\):

  • 对于 \(k\in[l,\min(j,r)]\) 的部分,为了防止记重,就只计算 \([l,k-1]\) 和 \(k\) 产生的逆序对数量。可以用树状数组搞定(不要每次都 \(O(B)\) 扫整个区间,这题卡常)。
  • 对于剩余部分,可以把这个块的所有元素和整个序列归并一下统一求出。需要注意 \(j\) 与 区间的前后位置。

然后对于每一次询问,整块的话贡献已经预处理出来了,注意整块与整块之间贡献会计重,所以对于第 \(i\) 块,设询问左端点所在块为 \(p\),就只需要加这个整块和两边散块的贡献,以及 \([p,i]\) 块与 \(i\) 的贡献。
然后考虑散块自身的贡献。预处理出 \(g[i]\) 表示 \(i\) 所在块左端点到 \(i\) 这段区间的逆序对数量,\(h[i]\) 表示 \(i\) 到 \(i\) 所在块右端点这段区间的逆序对数量。这个可以在求 \(f\) 的时候顺便求出。那么两边散快自身的贡献也预处理出来了,剩余两边散块之间的贡献,归并一下就好了。
如果询问的区间在同一个块内,记区间右端点为 \(p\),答案就是 \(h[l]-h[r+1]\) 再减去 \([l,r]\) 与 \([r+1,p]\) 这两部分的逆序对数。照旧一个归并搞定。
取 \(B=\sqrt{n}\),时间复杂度 \(O((n+m)\sqrt{n})\)。

但是显然这道题不会这么简单。我大概卡了以下这些地方的常数:

  • 注意到归并前都需要遍历整个块一次,记 \(b[i]\) 是原序列每一个块分别排序的后序列,会经常判断如果 l<=pos[b[i]]<=r 则将 \(b[i]\) 扔进归并序列里面。其中 \(pos[x]\) 表示 \(x\) 在原序列中的位置。这部分可以先预处理 \(pos1[i]=pos[b[i]]\),这样遍历的时候存址连续(大概是这么叫吧我不知道),至少可以快 \(60\rm ms\)。
  • 类似 \(f[N][\sqrt N]\) 的数组改为 \(f[\sqrt N][N]\)。估计可以快上百 \(\rm ms\)。
  • 上面简单提过的,预处理 \(f,g,h\) 虽然可以 \(O(nB)\) 搞定,但是还是写 \(O(n\log n)\) 的树状数组卡常。至少能快 \(200\rm ms\)。
  • 快读,开 O2。不必多说。试着在代码前加上 QuantAsk AK IOI。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=100010,B=320;
int n,m1,m2,Q,a[N],b[N],X[N],Y[N],pos[N],pos1[N],bel[N],L[N/B+5],R[N/B+5],f[N/B+5][N],g[N],h[N];
ll ans;

ll read()
{
	ll d=0; char ch=getchar();
	while (!isdigit(ch)) ch=getchar();
	while (isdigit(ch)) d=(d<<3)+(d<<1)+ch-48,ch=getchar();
	return d;
}

struct BIT
{
	int c[N];
	
	void add(int x,int v)
	{
		for (int i=x;i<=n;i+=i&-i)
			c[i]+=v;
	}
	
	int query(int x)
	{
		int res=0;
		for (int i=x;i;i-=i&-i)
			res+=c[i];
		return res;
	}
}bit;

int merge()
{
	int res=0;
	for (int i=1,j=1;i<=m1;i++)
	{
		while (j<=m2 && Y[j]<X[i]) j++;
		res+=j-1;
	}
	return res;
}

int main()
{
	n=read(); Q=read();
	for (int i=1;i<=n;i++)
		a[i]=b[i]=read(),pos[a[i]]=i;
	for (int i=1;i<=n/B+1;i++)
	{
		L[i]=R[i-1]+1; R[i]=min(n,B*i);
		sort(b+L[i],b+1+R[i]);
		for (int j=L[i];j<=R[i];j++)
		{
			bel[j]=i; f[i][j]=g[j]=j-L[i]-bit.query(a[j]);
			if (j>L[i]) g[j]+=g[j-1];
			bit.add(a[j],1);
		}
		for (int j=L[i];j<=R[i];j++) bit.add(a[j],-1);
		for (int j=R[i];j>=L[i];j--)
		{
			h[j]=h[j+1]+bit.query(a[j]);
			bit.add(a[j],1);
		}
		for (int j=L[i];j<=R[i];j++) bit.add(a[j],-1);
		for (int j=1,k=L[i];j<=n;j++)
		{
			while (k<=R[i] && b[k]<j) k++;
			if (pos[j]<L[i]) f[i][pos[j]]=k-L[i];
			if (pos[j]>R[i]) f[i][pos[j]]=R[i]-k+1;
		}
		for (int j=1;j<=n;j++) f[i][j]+=f[i][j-1];
	}
	for (int i=1;i<=n;i++) pos1[i]=pos[b[i]];
	while (Q--)
	{
		int l=read()^ans,r=read()^ans,bl=bel[l],br=bel[r];
		if (bl==br)
		{
			m1=m2=0;
			for (int i=L[bl];i<=R[bl];i++)
				if (pos1[i]>r) Y[++m2]=b[i];
					else if (pos1[i]>=l) X[++m1]=b[i];
			ans=h[l]-(r<R[bl] ? h[r+1] : 0)-merge();
			cout<<ans<<"\n";
			continue;
		}
		ans=h[l]+g[r];
		for (int i=bl+1;i<br;i++)
			ans+=f[i][R[i]]-f[i][l-1]+f[i][r]-f[i][L[br]-1];
		m1=m2=0;
		for (int i=L[bl];i<=R[bl];i++)
			if (pos1[i]>=l) X[++m1]=b[i];
		for (int i=L[br];i<=R[br];i++)
			if (pos1[i]<=r) Y[++m2]=b[i];
		ans+=merge();
		cout<<ans<<"\n";
	}
	return 0;
}
这篇关于【洛谷P5046】Yuno loves sqrt technology I的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!