【HDU】5145 NPY and girls 【分块】

传送门:【HDU】5145 NPY and girls


题目分析:裸分块- -,太久没写了,导致比赛时写错两个地方,交了13发,顺利拿到底线分- -

对于区间【L,R】,假设有不同的教室分别有数量a1,a2,a3,……,ak那么方案数即C(R - L + 1,a1)*C(R - L + 1 - a1,a2)*C(R - L + 1 - a1 - a2,a3)*……*C(R - L + 1 - a1 - a2 - …… - ak-1,ak),整理公式会发现方案数即(R - L + 1)的阶乘除以a1,a2,……,ak各自的阶乘。如果现在区间扩大变为【L,R+1】,ai增加了1,我们发现方案数变为【L,R】的方案数*(R + 1 - L + 1)/ 改变的教室改变后的数量。区间缩小与区间扩大类似。

知道了方法我们就可以用分块解决了,首先将n分成sqrt(n)块(编号0~sqrt(n)),然后将询问离线,将询问的左端点l除以sqrt(n)后放到对应的块中。然后排序时先以块编号为第一关键字排序,然后对块编号相同的询问的右坐标排序,保证相同块中右坐标单调即可。将【L,R】视为点(L,R),每个点向相邻的点转移时复杂度为O(1),可以证明的是从一个区间转移到另一个区间的均摊复杂度为O(sqrt(n)),所以总复杂度为O(n*sqrt(n))。


代码如下:


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

typedef long long LL ;

#define rep( i , a , b ) for ( int i = ( a ) ; i <  ( b ) ; ++ i )
#define For( i , a , b ) for ( int i = ( a ) ; i <= ( b ) ; ++ i )
#define rev( i , a , b ) for ( int i = ( a ) ; i >= ( b ) ; -- i )
#define clr( a , x ) memset ( a , x , sizeof a )

const int MAXN = 30005 ;
const int mod = 1e9 + 7 ;

struct Node {
	int l , r , pos , idx ;
	bool operator < ( const Node& t ) const {
		if ( pos != t.pos ) return pos < t.pos ;
		return r < t.r ;
	}
} ;

Node node[MAXN] ;
int cnt[MAXN] ;
int inv[MAXN] ;
int ans[MAXN] ;
int a[MAXN] ;
int n , m ;

int Pow ( int a , int b ) {
	LL res = 1 , tmp = a ;
	while ( b ) {
		if ( b & 1 ) res = res * tmp % mod ;
		tmp = tmp * tmp % mod ;
		b >>= 1 ;
	}
	return res ;
}

void solve () {
	clr ( cnt , 0 ) ;
	scanf ( "%d%d" , &n , &m ) ;
	int sqr = sqrt ( 1.0 * n ) ;
	For ( i , 1 , n ) scanf ( "%d" , &a[i] ) ;
	rep ( i , 0 , m ) {
		scanf ( "%d%d" , &node[i].l , &node[i].r ) ;
		node[i].idx = i ;
		node[i].pos = node[i].l / sqr ;
	}
	sort ( node , node + m ) ;
	int l = 1 , r = 0 , tmp = 0 ;
	LL res = 1 ;
	rep ( i , 0 , m ) {
		while ( r < node[i].r ) {
			++ r ;
			++ tmp ;
			++ cnt[a[r]] ;
			res = res * tmp % mod ;
			res = res * inv[cnt[a[r]]] % mod ;
		}
		while ( r > node[i].r ) {
			res = res * cnt[a[r]] % mod ;
			res = res * inv[tmp] % mod ;
			-- cnt[a[r]] ;
			-- tmp ;
			-- r ;
		}
		while ( l > node[i].l ) {
			-- l ;
			++ tmp ;
			++ cnt[a[l]] ;
			res = res * tmp % mod ;
			res = res * inv[cnt[a[l]]] % mod ;
		}
		while ( l < node[i].l ) {
			res = res * cnt[a[l]] % mod ;
			res = res * inv[tmp] % mod ;
			-- cnt[a[l]] ;
			-- tmp ;
			++ l ;
		}
		ans[node[i].idx] = res ;
	}
	rep ( i , 0 , m ) printf ( "%d\n" , ans[i] ) ;
}

int main () {
	int T ;
	scanf ( "%d" , &T ) ;
	rep ( i , 0 , MAXN ) inv[i] = Pow ( i , mod - 2 ) ;
	while ( T -- ) solve () ;
	return 0 ;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值