【洛谷P1494】【BZOJ2038】小Z的袜子【莫队】

题目大意:

题目链接:
洛谷:https://www.luogu.org/problemnew/show/P1494
BZOJ:https://www.lydsy.com/JudgeOnline/problem.php?id=2038

mm个询问,每次询问从[L,R][L,R]中选择两个元素相同的概率是多少。


思路:

  • 莫队算法

这道题是莫队的模板题。
莫队算法其实是很简单的。码量也不算大。可以做到离线O(nn)O(nsqrt{n})处理一些区间问题。
莫队的核心思想就是将询问排序,每次区间[l,r][l,r]的答案就从[l+1,r],[l1,r],[l,r+1],[l,r1][l+1,r],[l-1,r],[l,r+1],[l,r-1]转移来。
反正我不会证明复杂度XDcolor{white} exttt{反正我不会证明复杂度XD}
网上的讲解大部分都比较敷衍,这里就给出一篇比较好的讲解吧 链接


代码:

#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;

const int N=50010;
int n,m,T,l,r,a[N],pos[N];
ll s[N],ans;

struct Ask
{
	int l,r,id;
	ll ans1,ans2;
}ask[N];

bool cmp1(Ask x,Ask y)
{
	if (pos[x.l]<pos[y.l]) return 1;
	if (pos[x.l]>pos[y.l]) return 0;
	return x.r<y.r;
}

bool cmp2(Ask x,Ask y)
{
	return x.id<y.id;
}

void add(int x) 
{
	ans-=s[a[x]]*(s[a[x]]-1)/2;
	if (x) s[a[x]]++;
	ans+=s[a[x]]*(s[a[x]]-1)/2;
}

void dev(int x)
{
	ans-=s[a[x]]*(s[a[x]]-1)/2;
	if (x) s[a[x]]--;
	ans+=s[a[x]]*(s[a[x]]-1)/2;
}

int main()
{
	scanf("%d%d",&n,&m);
	T=(int)sqrt(m);
	if (m%T) T++;
	for (int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	for (int i=1;i<=m;i++)
	{
		scanf("%d%d",&ask[i].l,&ask[i].r);
		ask[i].id=i;
		pos[i]=(i-1)/T+1;
	}
	sort(ask+1,ask+1+m,cmp1);
	for (int i=1;i<=m;i++)
	{
		for (;l<ask[i].l;l++) dev(l);
		for (;l>ask[i].l;l--) add(l-1);
		for (;r<ask[i].r;r++) add(r+1);
		for (;r>ask[i].r;r--) dev(r);
		if (l==r) ask[i].ans1=0,ask[i].ans2=1;
		else
		{
			ll k=ask[i].r-ask[i].l+1;
			ll a1=ans,a2=k*(k-1)/2;
			ll GCD=__gcd(a1,a2);
			ask[i].ans1=a1/GCD;
			ask[i].ans2=a2/GCD;
		}
	}
	sort(ask+1,ask+1+m,cmp2);
	for (int i=1;i<=m;i++)
		printf("%lld/%lld
",ask[i].ans1,ask[i].ans2);
	return 0;
}
原文地址:https://www.cnblogs.com/hello-tomorrow/p/11998187.html