HDOJ 5145 NPY and girls

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5145

题目大意:给我们两个数n,q,分别代表有n个女生以及q个询问,然后给出n个女生所在的教室号,接着给出q个询问,每一个询问都是[L,R],问我们在1~n的女生中访问L~R这几个女生可以的方案数,由于女生可能会几个出现在同一个教室,所以我们可以用排列组合去求解。

设在L~R这个区间中一共有m个教室的女生,一共有N个女生,每一个教室都有ci个女生,则方案数就是C(N, c1)*C(N-c1, c2)*C(N-c1-c2, c3)……C(cm, cm)。

显然暴力求组合数肯定是要超时的,所以我们要分块。

首先,小编先讲解一个关于组合数的推导:

1. C(m+1,n+1) = C(m,n)*(m+1/n+1)

2. C(m,n) = C(m+1,n+1)*(n+1/m+1)

1和2其实是一样的,写法不同而已,这两个式子在小编下面的推导中要用到。

假如我们知道了[l,r]的答案,则我们可以求得[l,r+1]:

cnt[i]表示在第i个教室的女生的个数,那么a[r+1]对答案的贡献相当于小编刚刚给出的组合数性质的第一个。

cnt[a[r]]++;     tmp = tmp*(r-l+1)%MOD*inv[cnt[a[r]]]%MOD;

假如我们知道了[l,r]的答案,则我们可以求得[l-1,r]:

cnt[i]表示在第i个教室的女生的个数,那么a[l-1]对答案的贡献相当于小编刚刚给出的组合数性质的第二个。

tmp = tmp*cnt[a[r]]%MOD*inv[r-l+1]%MOD;    cnt[a[r]]--;


那么我们知道[p,q]的答案,就可以反复运用上面小编给出的这两步推出[L,R]的答案。

接下来我们就可以用到莫队算法,把这n个数分成sqrt(n)个区间,算出这个q个询问的左端在那一块:pos = L/sqrt(n),在记录下每一个询问的顺序之后,对这q个询问进行排序,以pos作为第一关键字,R作为第二关键字。然后我们就可以按照新的询问顺序求出所有询问的答案。


我们这个方法的复杂度为O(n^1.5)。


相信很多不太懂分块原理和莫队算法的同学都不知道这是为什么,也有可能有些同学连为什么要这样分块,为什么不直接按左端为第一关键字排序,小编这里就顺便讲解一下吧。


1)对于那些左端点属于同一个块的询问

  i)因为r是递增的,最差情况q从块的最左端加到nO(n),一共n^0.5个块,所以这部分的复杂度是O(n^1.5)

  ii)p一次询问最多变化N^0.5,所以这部分的时间复杂度是O(Q*n^0.5)

2)从一个块跨越到另外一个块时

  i)p最多变化2*n^0.5,最多跨越n^0.5,这部分复杂度是O(n)

  ii)最差情况下,q从n移动到最左端

n和Q范围一样,所以整个算法复杂度是O(n^1.5)


这就是我们分块的神奇之处,复杂度为O(n^1.5)

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int maxn = 10000;
const LL MOD = 1e9+7;
LL Pow_mod(LL a,LL b)
{
	LL ans = 1;
	while(b)
	{
		if(b&1) ans = (ans*a)%MOD;
		b >>= 1;
		a = (a*a)%MOD;
	}
	return ans;
}
LL Inv(LL a)
{
	return Pow_mod(a,MOD-2);
}
int per;
struct Query
{
	int l,r,id;
	bool operator < (const struct Query a)const
	{
		return l/per == a.l/per? r < a.r: l/per < a.l/per;
	}
};
Query query[30030];
LL ans[30030],inv[30030];
int a[30030],cnt[30030];
int main()
{
	for(int i = 1; i <= 30000; ++i)
		inv[i] = Inv(i);
	int t,n,q;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&q);
		per = sqrt(n*1.0);
		for(int i = 1; i <= n; ++i)
			scanf("%d",&a[i]);
		for(int i = 0; i < q; ++i)
		{
			scanf("%d%d",&query[i].l,&query[i].r);
			query[i].id = i;
		}
		sort(query,query+q);
		int l = 1, r = 0;
		LL tmp = 1;
		memset(cnt,0,sizeof(cnt));
		for(int i = 0; i < q; ++i)
		{
			while(r < query[i].r)
			{
				++r;
				cnt[a[r]]++;
				tmp = tmp*(r-l+1)%MOD*inv[cnt[a[r]]]%MOD;
			}
			while(l > query[i].l)
			{
				--l;
				cnt[a[l]]++;
				tmp = tmp*(r-l+1)%MOD*inv[cnt[a[l]]]%MOD;
			}
			while(r > query[i].r)
			{
				tmp = tmp*cnt[a[r]]%MOD*inv[r-l+1]%MOD;
				cnt[a[r]]--;
				--r;
			}
			while(l < query[i].l)
			{
				tmp = tmp*cnt[a[l]]%MOD*inv[r-l+1]%MOD;
				cnt[a[l]]--;
				++l;
			}
			ans[query[i].id] = tmp;
		}
		for(int i = 0; i < q; ++i)
			printf("%I64d\n",ans[i]);
	}
	return 0;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值