HDU 4622 莫队算法+逆元

题意

有N个girl,一个人要去见这N个girl。这些girl分别在某个教室中。每次询问一个L和R,代表这个人要去访问第L到第R个girl,问访问教室的序列有多少种排序方式。

题解

区间L到R内有1号元素有num[1]个,N号元素有num[N]个,那么区间L到R的组合方式有(R-L+1)!/num[1]!* .. num[n]!种。(C(num[1],r-l+1) C(num[2],r-l+1) * .. * C(num[n],r-l+1))。如果再加一个元素的话,假设加的元素为1,那么组合方式就要 * (R-L+2)/(num[1]+1) 。很明显已经可以用莫队了,这里比较郁闷的是有一个除法,那么我们就使用一下费马小定理将除法转化为乘法。

注意事项

需要注意的是费马小定理很慢,所以我们需要打表来进行优化。另外就是一定要注意先确定R边界,再确定L边界,否则会有R-L+1<0的情况发生。

代码

#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#include<string>
#include<set>
#include<map>
#include<bitset>
#include<stack>
#include<string>
#define UP(i,l,h) for(int i=l;i<h;i++)
#define DOWN(i,h,l) for(int i=h-1;i>=l;i--)
#define W(a) while(a)
#define MEM(a,b) memset(a,b,sizeof(a))
#define LL long long
#define INF 0x3f3f3f3f
#define MAXN 30050
#define MOD 1000000007
#define EPS 1e-3
#define int LL
using namespace std;
int a[MAXN],num[MAXN],iv[MAXN];
int block;int n,m;

struct Node{
    int l,r,id;
    LL ans;
}nodes[MAXN];

LL quick_pow(LL a,LL b){
    if(b<0) return 0;
    LL ret=1;
    a%=MOD;
    for(;b;b>>=1,a=(a*a)%MOD) if(b&1) ret=(ret*a)%MOD;
    return ret;
}

LL inv(LL a){
    return quick_pow(a,MOD-2);
}

void solve(){
    MEM(num,0);
    int l=1,r=1;
    LL val=1;
    num[a[1]]=1;
    UP(i,0,m){
        for(;r>nodes[i].r;r--) val=((val*num[a[r]])%MOD*iv[r-l+1])%MOD,num[a[r]]--;
        for(;r<nodes[i].r;r++) num[a[r+1]]++,val=((val*(r-l+2))%MOD*iv[num[a[r+1]]])%MOD;
        for(;l<nodes[i].l;l++) val=((val*num[a[l]])%MOD*iv[r-l+1])%MOD,num[a[l]]--;
        for(;l>nodes[i].l;l--) num[a[l-1]]++,val=((val*(r-l+2))%MOD*iv[num[a[l-1]]])%MOD;
        nodes[i].ans=val;
    }
}

bool cmp(Node a,Node b){
    return a.l/block==b.l/block?a.r<b.r:a.l<b.l;
}

bool cmp1(Node a,Node b){
    return a.id<b.id;
}

main(){
    int t;
    scanf("%I64d",&t);
    UP(i,0,30010) iv[i]=inv(i);
    W(t--){
        scanf("%I64d%I64d",&n,&m);
        UP(i,1,n+1) scanf("%I64d",&a[i]);
        block=sqrt(1.0*n);
        UP(i,0,m) scanf("%I64d%I64d",&nodes[i].l,&nodes[i].r),nodes[i].id=i;
        sort(nodes,nodes+m,cmp);
        solve();
        sort(nodes,nodes+m,cmp1);
        UP(i,0,m) printf("%I64d\n",nodes[i].ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值