【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;
}