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

洛谷 P3477 [POI2008]PER-Permutation 解题报告的更多相关文章

  1. 洛谷_Cx的故事_解题报告_第四题70

    1.并查集求最小生成树 Code: #include <stdio.h> #include <stdlib.h>   struct node {     long x,y,c; ...

  2. 洛谷 P2317 [HNOI2005]星际贸易 解题报告

    P2317 [HNOI2005]星际贸易 题目描述 输入输出格式 输入格式: 输出格式: 如果可以找到这样的方案,那么输出文件output.txt中包含两个整数X和Y.X表示贸易额,Y表示净利润并且两 ...

  3. 洛谷 P3802 小魔女帕琪 解题报告

    P3802 小魔女帕琪 题目背景 从前有一个聪明的小魔女帕琪,兴趣是狩猎吸血鬼. 帕琪能熟练使用七种属性(金.木.水.火.土.日.月)的魔法,除了能使用这么多种属性魔法外,她还能将两种以上属性组合,从 ...

  4. 洛谷 P2606 [ZJOI2010]排列计数 解题报告

    P2606 [ZJOI2010]排列计数 题目描述 称一个\(1,2,...,N\)的排列\(P_1,P_2...,P_n\)是\(Magic\)的,当且仅当对所以的\(2<=i<=N\) ...

  5. 洛谷1303 A*B Problem 解题报告

    洛谷1303 A*B Problem 本题地址:http://www.luogu.org/problem/show?pid=1303 题目描述 求两数的积. 输入输出格式 输入格式: 两个数 输出格式 ...

  6. 洛谷 P3084 [USACO13OPEN]照片Photo 解题报告

    [USACO13OPEN]照片Photo 题目描述 农夫约翰决定给站在一条线上的\(N(1 \le N \le 200,000)\)头奶牛制作一张全家福照片,\(N\)头奶牛编号\(1\)到\(N\) ...

  7. 洛谷 P1379 八数码难题 解题报告

    P1379 八数码难题 题目描述 在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字.棋盘中留有一个空格,空格用0来表示.空格周围的棋子可以移到空格中.要求解的问题是:给出一种初始布局(初 ...

  8. NOIP2015 D2T3 洛谷2680 BZOJ4326 运输计划 解题报告

    前言:个人认为这是历年NOIP中比较简单的最后一题了,因此将自己的思路与大家分享. 题目大意: 给一棵无根树,给出m条路径.允许将树上的一条边的权值改为0.求m条路径长度最大值的最小值.n,m< ...

  9. 洛谷 P1129 [ZJOI2007]矩阵游戏 解题报告

    P1129 [ZJOI2007]矩阵游戏 题目描述 小\(Q\)是一个非常聪明的孩子,除了国际象棋,他还很喜欢玩一个电脑益智游戏――矩阵游戏.矩阵游戏在一个\(N*N\)黑白方阵进行(如同国际象棋一般 ...

随机推荐

  1. scrapy爬取伯乐在线文章数据

    创建项目 切换到ArticleSpider目录下创建爬虫文件 设置settings.py爬虫协议为False 编写启动爬虫文件main.py

  2. (数据科学学习手札14)Mean-Shift聚类法简单介绍及Python实现

    不管之前介绍的K-means还是K-medoids聚类,都得事先确定聚类簇的个数,而且肘部法则也并不是万能的,总会遇到难以抉择的情况,而本篇将要介绍的Mean-Shift聚类法就可以自动确定k的个数, ...

  3. python2.7练习小例子(十一)

        11):题目:判断101-200之间有多少个素数,并输出所有素数.     程序分析:判断素数的方法:用一个数分别去除2到sqrt(这个数),如果能被整除,则表明此数不是素数,反之是素数.   ...

  4. docker学习(三) 安装docker的web可视化管理工具

    1.docker是一个一款很轻便的应用容器引擎,为了更好的管理和使用docker,使用web可视化管理工具似乎更符合大多数人的需求.在这里,我给大家分享下自己使用过的几款web工具:docker UI ...

  5. C# 控制台应用程序输出颜色字体

    最佳解决方案的代码: static void Main(string[] args) { Console.ForegroundColor = ConsoleColor.Green; Console.W ...

  6. 批处理bat实现创建、复制、删除文件及文件夹

    转自:http://blog.csdn.net/linda1000/article/details/10221285 1 建bat文件自动执行复制,删除命令. 例1:以下是复制cd.dll文件至win ...

  7. C++11中Lambda的使用

    Lambda functions: Constructs a closure, an unnamed function object capable of capturing variables in ...

  8. Memcached Hash算法

    本文来自网易云社区 作者:吕宗胜 Hash算法 1. Memcached Hash介绍 我们在前面的文章中已经介绍过了Memcached的内存管理方式,LRU的策略.由于Memcached的数据存储方 ...

  9. svn 用cmd命令行启动服务

    部署好svn 服务器后,用cmd命令行 svnserve -d -r [仓库地址] 启动服务,这样别的用户可以通过网络访问svn服务器了.

  10. 跳出for循环break和continue的区别

    1.break 跳出for循环,结束for循环 如果有两层循环,break只能跳出一层循环 2.continue 跳出本次循环,继续下一条数据的循环