[POI2008]PER-Permutation 非质数取模

PER-Permutation

Question

求一个有重复元素的排列的字典序


Solution

类似于康托展开
从后往前算出比每一位小的排列的个数
累加起来便是字典序,只不过这里的个数要考虑到重复元素

这里主要讲取模(因为这里模数不一定是质数)

推出式子后,发现每次需要乘 ni n − i ,还要除以 cnt[a[i]] c n t [ a [ i ] ]

主要步骤是将模数分解为 m=Πki=1pni m = Π i = 1 k p n i
k k 次求答案,记为a1...k(每次对 pni p n i 取模),最后将答案合并成 Ans A n s
相当于解下面这一组同余方程
Ansa1(mod A n s ≡ a 1 ( m o d pn1) p n 1 )
Ansa2(mod A n s ≡ a 2 ( m o d pn2) p n 2 )

Ansak(mod A n s ≡ a k ( m o d pnk) p n k )
用扩欧或用中国剩余定理合并

要注意一种特殊情况
因为 pni p n i 毕竟不是质数,它可能和分母有公因子,所以要先除掉分子分母所有的 p p <script type="math/tex" id="MathJax-Element-2569">p</script>(记录有多少)取完模在乘回来

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define For(i,s,e) for(int i=(s); i<=(e); i++)
#define Rep(i,s,e) for(int i=(s); i>=(e); i--)
#define Lowbit(x) ((x)&(-x))
using namespace std;

typedef long long LL;
typedef pair<int, int> pii;
const int N = 300000 + 5;

int n, Mod, a[N], cnt[N], aMax;

LL T[N], Ans;

void Modify(int p){ while(p <= aMax + 1) T[p]++, p += Lowbit(p); }
int Query(int p){
    if(!p) return 0;
    LL Ans = 0;
    while(p) Ans += T[p], p -= Lowbit(p);
    return Ans;
}

int tot, Base[N], Pw[N], Mi[N];

void Divide(int x){
    For(i, 2, x){
        int _Pw = 1;
        while(x && x % i == 0) x /= i, _Pw *= i;

        if(_Pw != 1) Base[++tot] = i, Pw[tot] = _Pw;
    }
}

struct NewNum{
    int a, b;
    void init(int x, int p){// x = a * p ^ b
        a = x; b = 0;
        while(a && a % p == 0) a /= p, b++;
    }
}Trans;

void exGcd(int a, int b, LL & x, LL & y){
    if(!b){ x = 1, y = 0; return; }

    LL x1, y1;
    exGcd(b, a % b, x1, y1);
    x = y1, y = x1 - (a / b) * y1;
}
int Inv(int a, int p){ //a ^ -1 
    LL x, y;
    exGcd(a, p, x, y);
    return (x + p) % p;
}

int Calc(int p, int m){//->  % m
    For(i, 1, max(aMax, n) + 1) T[i] = cnt[i] = 0;

    int Ans = 1, F = 1; Mi[0] = 1; cnt[a[n]]++;
    Modify(a[n]);

    For(i, 1, max(aMax, n)) Mi[i] = Mi[i - 1] * p % m;

    int num = 0;
    Rep(i, n - 1, 1){
        Trans.init(n - i, p);
        num += Trans.b;
        F = (1LL * F * Trans.a) % m;

        Trans.init(++cnt[a[i]], p);
        num -= Trans.b;
        F = (1LL * F * Inv(Trans.a, m)) % m;

        Modify(a[i]);
        Ans = (Ans + 1LL * F * Query(a[i] - 1)% m * Mi[num] ) % m;
    }
    return Ans;
}

int main(){
    freopen("test.in","r",stdin);
    freopen("test.out","w",stdout);

    cin>>n>>Mod;
    Divide(Mod);

    For(i, 1, n) scanf("%lld",&a[i]), aMax = max(aMax, a[i]);

    Ans = Calc(Base[1], Pw[1]);
    int M = Pw[1];

    For(i, 2, tot){
        /*中国剩余定理*/
/*      int a = Calc(Base[i], Pw[i]);
        int M = Mod / Pw[i];
        Ans = (Ans + (LL)a * M % Mod * Inv(M, Pw[i]))%Mod;*/

        /*扩欧*/
        LL x, y;
        exGcd(M, Pw[i], x, y);
        x *= Calc(Base[i], Pw[i]) - Ans;
        Ans += x * M;
        M *= Pw[i];
        Ans = (Ans % Mod + M) % M;
    }
    cout<<(Ans + Mod) % Mod<<endl;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值