【BZOJ】P1129 PER

数论+组合计数

题目链接

一道鬼题!

part1.不看取模,假设所有元素互不相等

显然,这是一个康托展开。

int cantor(int a[],int n){//cantor展开,n表示是n位的全排列,a[]表示全排列的数(用数组表示)
    int ans=0,sum=0;
    for(int i=1;i<n;i++){
        for(int j=i+1;j<=n;j++)
            if(a[j]<a[i])
                sum++;
        ans+=sum*factorial[n-i];//累积
        sum=0;//计数器归零
    }
    return ans+1;
}

由于\(n\le 300000\),所以求有多少个数小于\(a[i]\)要用树状数组优化。


part2.不看取模,所有元素可以有一部分相等

考虑一个元素x出现了\(cnt[x]\)次,那么其对答案的贡献就是\(cnt[x]!\),所以当前第i位的贡献就是

\[\frac{sum \cdot (n-i)!}{\prod cnt[j]!} \]

注意:我们要先把\(cnt[A[i]]++\)再计算。

请看栗子:

序列: 2 4 2 1

算第1位时,发现有1个数1比\(a[1]\)小,因此在此之前有1开头的。

即1 2 2 4 ,1 2 4 2,1 4 2 2,个数为\(\frac{1 \cdot 3!}{2!}\),但如果不先把\(cnt[2]++\)的话,计算时\(cnt[2]=1\),显然不行

那么这部分也完美解决了。


part3.取模,所有元素可以有一部分相等

你可能会认为,取模,不就是搞个模逆元就行了嘛。

错了,这里的模数m,并不是我们常用的1e9+7,也就是说其不一定是个质数,那么显然费小是行不通的。

那咋办嘛?

观察一下这个式子:

\[\frac{sum \cdot (n-i)!}{\prod cnt[j]!} \]

显然,这是个整数。那么,我们将分母和分子进行质因子分解,对于每个质因子,肯定在分母中的数量大于等于分子中的数量。

那么,我们先提出m的质因子,再在该式子的分母和分子中提出m中含有的质因子。

也就是说,我们把式子拆成两部分:

\[X\cdot \frac{A}{B} \]

其中,A和B不含m的质因子。

这样,提完后分子就与m互质了,然后就可以愉快地模逆元了~~

注意:m不一定是质数,因此还得用拓欧去求

代码:

#include<bits/stdc++.h>
#define ll long long
#define int long long
#define MAXN 1000010
using namespace std;
ll n,m,A[MAXN],BIT[MAXN],cnt[MAXN],p[101],CntMum[101],CntSon[101],maxn;
ll mum=1,son=1,ans;
inline void Add(ll i,ll x){
    while(i<=maxn)BIT[i]+=x,i+=i&-i;
}
inline ll Query(ll i){
    ll res=0;
    while(i>=1)res+=BIT[i],i-=i&-i;
    return res;
}
inline void updateSon(ll x){
    if(!x)return;
    for(register int i=1;i<=p[0];++i){
        while(x%p[i]==0){
            CntSon[i]++;
            x/=p[i];
        }
    }
    son=son*1LL*x%m;
}
inline void updateMum(ll x){
    if(!x)return;
    for(register int i=1;i<=p[0];++i){
        while(x%p[i]==0){
            CntMum[i]++;
            x/=p[i];
        }
    }
    mum=mum*1LL*x%m;
}
inline ll update2(ll x,ll pos){
    if(!x)return 0;
    for(register int i=1;i<=p[0];++i){
        while(x%p[i]==0){
            CntMum[i]+=pos;
            x/=p[i];
        }
    }
    return x;
}
inline ll Quick_Pow(ll a,ll p){
    ll res=1;
    while(p){
        if(p&1)res=res*a%m;
        a=a*a%m;
        p>>=1;
    }
    return res;
}
inline void exgcd(ll a,ll b,ll &x,ll &y){
    if(b==0){
        x=1,y=0;
        return;
    }
    exgcd(b,a%b,y,x);
    y=(y-(a/b)*x%m+m)%m;
}
signed main(){
    scanf("%lld %lld",&n,&m);
    for(register int i=1;i<=n;i++)scanf("%lld",&A[i]),maxn=max(maxn,A[i]);
    int x=m;
    for(register int i=2;i*i<=x;i++){
        if(x%i==0)p[++p[0]]=i;
        while(x%i==0)x/=i;
    }
    if(x!=1)p[++p[0]]=x;
    ll Ans=1,X,Y;
    for(register int i=n;i>=1;--i){
        Ans=1;
        ++cnt[A[i]];
        updateSon(cnt[A[i]]);
        if(n!=i)updateMum(n-i);
        Add(A[i],1);
        register ll num=Query(A[i]-1),tmp;
        if(num==0)continue;
        tmp=update2(num,1);
        for(register int j=1;j<=p[0];++j)Ans=Ans*Quick_Pow(p[j],CntMum[j]-CntSon[j])%m;
        exgcd(son,m,X,Y);
        Ans=Ans*(tmp*1LL*mum%m)%m*X%m;
        update2(num,-1);
        ans+=Ans;
        (ans>=m)&&(ans-=m);
    }
    cout<<(ans+1)%m;
    return 0;
}
posted @ 2019-09-10 22:24  TieT  阅读(172)  评论(0编辑  收藏  举报