洛谷 P3477 [POI2008]PER-Permutation 解题报告

博客围绕P3477 [POI2008]PER - Permutation题目展开,介绍多重集排列概念,要求根据给定多重集排列和整数m,求排列排名取模m。通过讨论每一位数值贡献,类似康托展开求解,因模数不一定是质数,需对模数唯一分解,用CRT合并,还可用树状数组维护。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

P3477 [POI2008]PER-Permutation

题目描述

Multiset is a mathematical object similar to a set, but each member of a multiset may have more than one membership.

Just as with any set, the members of a multiset can be ordered in many ways. We call each such ordering a permutation of the multiset. For example, among the permutations of the multiset{1,1,2,3,3,3,7,8}. there are {2,3,1,3,3,7,1,8} and{8,7,3,3,3,2,1,1}.

We will say that one permutation of a given multiset is smaller (in lexicographic order) than another permutation, if on the first position that does not match the first permutation has a smaller element than the other one. All permutations of a given multiset can be numbered (starting from one) in an increasing order.

Task Write a programme that reads the description of a permutation of a multiset and a positive integerm from the standard input, determines the remainder of the rank of that permutation in the lexicographic ordering modulo m, writes out the result to the standard output.

多重集合是数学中的一个概念,它的定义很像集合,但是在多重集之中,同一个元素可以出现多次。

和集合一样,多重集的的元素可以有很多种元素的排布顺序。我们把它叫作多重集的排列。

现在我们定义多重集的某个排列\(s_i\)比某个排列\(s_j\)的大小比较为字典序比较。这样某个多重集的排列可以从小到大得排起来。

现在给你一个元素个数为n的多重集的一个排列和\(m\),求这个排列的排名取模\(m\)

输入输出格式

输入格式:

The first line of the standard input holds two integers n( \(1\le n \le 300000\)) and m ( \(2 \le m \le 10^9\)) ,separated by a single space. These denote, respectively, the cardinality of the multiset and \dots the number m.

The second line of the standard input contains n positive integers \(a_i\) (\(1\le a_i \le 300000\)), separated by single spaces and denoting successive elements of the multiset permutation.

第一行 两个整数n,m

第二行 n个数,代表多重集的排列

输出格式:

The first and only line of the standard output is to hold one integer, the remainder modulo m of the rank of the input permutation in the lexicographic ordering.

一行一个整数 排名取模m


一句话题意:求有重复元素的排列的排名,模数不一定是质数

我们找到字典序比它小的排列的个数

讨论每一位数值的贡献,像康托展开那样

设给出排列为 \(a_1,a_2,a_3,...a_n\),字典序比它小的排列为\(b_1,b_2,b_3,...b_n\)

考虑从左到右第\(i\)位的贡献

\(b_i=a_i\) 右边的是子问题

\(b_i<a_i\),则设右边的从小到大每个元素出现的次数分别为\(c_1,c_2,...,c_m\)

\(b_i\)出现\(c_j\)

则当\(b_i\)这个数值放在第\(i\)位置,右边的全排列的贡献为

\(\frac{(n-i)!}{c_1! \times c_2 ! \times ... \times c_m!} \times c_j\)

则所有可以放到第一位的数的贡献和为

\(\frac{(n-i)!}{c_1! \times c_2 ! \times ... \times c_m!} \times \sum_{b_i<a_i}c_j\)(\(b_i\)这里代表数值意义,一个数值只出现一次)

我们处理每一位这样的

后面的好处理,树状数组维护一下就行了

前面的因为模数不为质数可能没有逆元,所有我们先把模数唯一分解,对每一个质因子的多少次方做,然后CRT进行合并

在某个质因子多少次方下做时,我们把数字拆成 其他项 和 这个质因子多少次方 就行了

因为答案一定是整数,所以分子的质因子个数一定大于分母的,我们把这些先拿出来,就可以求逆元啦,然后快速幂乘回去就行

注意小细节


Code:

#include <cstdio>
#include <cstring>
#define ll long long
const int N=3e5+10;
ll a[N],m,n,buct[N],id[N],cnt0[N];
ll fac[N],ct[N],mx,sum[N],mod;
void exgcd(ll a,ll b,ll &x,ll &y)//只是一个exgcd..
{
    if(!b){x=1,y=0;return;}
    exgcd(b,a%b,x,y);
    ll tmp=x;
    x=y;
    y=tmp-a/b*y;
}
ll inv(ll a,ll p)//只是一个求逆元
{
    ll x,y;
    exgcd(a,p,x,y);
    return (x%p+p)%p;
}
ll CRT(ll a,ll b)//只是CRT的一项
{
    return mod/b*a%mod*inv(mod/b,b)%mod;
}
ll quick_pow(ll d,ll k,ll p)//只是一个快速幂
{
    ll f=1;
    while(k)
    {
        if(k&1) f=f*d%p;
        d=d*d%p;
        k>>=1;
    }
    return f;
}
ll query(ll x)
{
    ll s;for(s=0;x;x-=x&-x) s+=sum[x];
    return s;
}
void add(ll x)
{
    while(x<=m) ++sum[x],x+=x&-x;
}
ll cal(ll d,ll p)
{
    ll ans=0;fac[0]=1;
    memset(ct,0,sizeof(ct));
    for(ll i=1;i<=mx;i++)
    {
        ll num=i;
        while(num%d==0) num/=d;
        (fac[i]=fac[i-1]*num%p)%=p;
        for(ll j=i;j;j/=d) ct[i]+=j/d;
    }
    memset(cnt0,0,sizeof(cnt0));
    memset(sum,0,sizeof(sum));
    cnt0[a[n]]++;add(a[n]);
    ll den=1,cn=0;
    for(ll i=n-1;i;i--)
    {
        ll cc=++cnt0[a[i]];
        add(a[i]);
        if(cc>1)
        {
            (den*=fac[cc-1]*inv(fac[cc],p)%p)%=p;
            cn+=ct[cc]-ct[cc-1];
        }
        ll k=query(a[i]-1);
        (ans+=fac[n-i]*den%p*k%p*(k?quick_pow(d,ct[n-i]-cn,p):0)%p)%=p;
    }
    return ans;
}
ll calp(ll p)
{
    ll ans=0;
    fac[0]=1;
    for(ll i=1;i<=mx;i++)
        fac[i]=(fac[i-1]*i)%p;
    memset(cnt0,0,sizeof(cnt0));
    memset(sum,0,sizeof(sum));
    cnt0[a[n]]++;add(a[n]);
    ll den=1;
    for(ll i=n-1;i;i--)
    {
        ll cc=++cnt0[a[i]];
        add(a[i]);
        if(cc>1) (den*=fac[cc-1]*inv(fac[cc],p)%p)%=p;
        (ans+=fac[n-i]*den%p*query(a[i]-1)%p)%=p;
    }
    return ans;
}
ll work(ll p)
{
    ll ans=0;
    for(ll i=2;i*i<=p;i++)
    {
        if(p%i==0)
        {
            ll d=1;
            while(p%i==0)
                p/=i,d*=i;
            if(i==d)
                (ans+=CRT(calp(i)+1,p))%=mod;
            else
                (ans+=CRT(cal(i,d)+1,d))%=mod;
        }
    }
    if(p!=1) (ans+=CRT(calp(p)+1,p))%=mod;
    return ans;
}
int main()
{
    //freopen("data.in","r",stdin);
    scanf("%lld%lld",&n,&mod);
    for(ll i=1;i<=n;i++) scanf("%lld",a+i),buct[a[i]]++;
    for(ll i=1;i<=N-10;i++) if(buct[i]) id[i]=++m;
    for(ll i=1;i<=n;i++)
    {
        mx=mx>buct[a[i]]?mx:buct[a[i]];
        a[i]=id[a[i]];
    }
    mx=mx>n?mx:n;
    printf("%lld\n",work(mod));
    return 0;
}

2018.8.27

转载于:https://www.cnblogs.com/butterflydew/p/9543393.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值