P3477 [POI2008] PER-Permutation 解题报告
可能更好的阅读体验
(upd:对 Hack 的有关分析已更新,见上个人博客↑)
解题思路:
首先看到这道题求排名,感觉还挺像康托展开的,但是康拓展开求的是 不会出现重复元素的排列 的排名,而这道题的难题主要有两个:
可重复和未知模数。
对于可能重复的元素:
我们思考康托展开本身的公式。
对于一个
而
int ans=0;
for(int i=1;i<=n;i++){
int sum=0;
for(int j=i;j<=n;j++)
if(a[i]<a[j])sum++;//统计sum
ans=(ans+sum*jc[n-i])%998244353;//计算ans
}
printf("%d",ans+1);//别忘了+1
那考虑如何去重呢?
我们的
因此,我们考虑将重复元素提出来,例如
所以,设从
然后你发现,如果从左往右扫,不断删除元素对于
对于不明的模数
对于不定模数,或许有两种解决方式:
-
使用不依赖模数性质的算法或利用给出的模数自带的性质。
-
对模数进行特殊的处理。
不知道数据有没有 114514 一类的模数,但是显然 114514 不是质数,所以费马小定理不能使用。
而我们只能考虑扩展欧几里得算法了,它要求我们求解的模数与求解数互质。
所以我们可以对分母
对于共同的质因子,我们考虑直接消去,因为
(tips:有关Hack,请注意这里的分子包含
使用树状数组优化一下,跑的还是比较快的。
#include<bits/stdc++.h>
#define il inline
#define rg register int
#define cout std::cout
#define cerr std::cerr
#define endl '\n'
#define int long long
typedef long long ll;
typedef unsigned long long ull;
typedef double ff;
typedef long double llf;
const ff eps=1e-8;
int Max(int x,int y) <% return x<y?y:x; %>
int Min(int x,int y) <% return x<y?x:y; %>
int Abs(int x) <% return x>0?x:-x; %>
#if ONLINE_JUDGE
char INPUT[1<<20],*p1=INPUT,*p2=INPUT;
#define getchar() (p1==p2 && (p2=(p1=INPUT)+fread(INPUT,1,1<<20,stdin),p1==p2)?EOF:*p1++)
#endif
il int read(){
char c=getchar();
int x=0,f=1;
while(c<48) <% if(c=='-')f=-1;c=getchar(); %>
while(c>47) x=(x*10)+(c^48),c=getchar();
return x*f;
}const int maxn=3e5+5;
int n,Mod,ptot,p[maxn],a[maxn],sa[maxn],rank[maxn],len;
int cnt_numerator[maxn],cnt_denominator[maxn];
ll sum[maxn],ans,cnt[maxn];
il ll mymod(ll x){ return x<Mod?x:x-Mod; }
il ll qpow(ll x,int k){
// x^k
ll res=1;
while(k){
if(k&1) res=res*x%Mod;
x=x*x%Mod;
k=k>>1;
}
return res;
}
il void exgcd(int a,int b,int &x,int &y){
// 求得x是a在模Mod意义下的乘法逆元
if(!b) return x=1,y=0,void();
exgcd(b,a%b,y,x);y=(y-(a/b)*x%Mod+Mod)%Mod;
}
#define lowbit(x) (x&-x)
il ll query(int pos){
ll res=0;
while(pos){
res+=sum[pos];
pos-=lowbit(pos);
}
return res;
}
il void update(int pos,int val){
while(pos<=len){
sum[pos]+=val;
pos+=lowbit(pos);
}
}
#undef lowbit
il ll solve_numerator(ll numerator){
// 对分子分解质因数
if(!numerator) return 1;
for(rg i=1;i<=ptot;++i){
if(numerator%p[i]) continue;
while(!(numerator%p[i])) ++cnt_numerator[i],numerator/=p[i];
}
return numerator;
}
il void back(ll numerator){
// 撤回ssum对cnt_numerator的影响
if(!numerator) return;
for(rg i=1;i<=ptot;++i){
if(numerator%p[i]) continue;
while(!(numerator%p[i])) --cnt_numerator[i],numerator/=p[i];
}
}
il ll solve_denominator(ll denominator){
// 对分母分解质因数
if(!denominator) return 1;
for(rg i=1;i<=ptot;++i){
if(denominator%p[i]) continue;
while(!(denominator%p[i])) ++cnt_denominator[i],denominator/=p[i];
}
return denominator;
}
il void work(){
int numerator=1,denominator=1,res=1,x=0,y=0,save;
for(rg i=n;i>=1;--i){
res=1;
++cnt[a[i]];
numerator=numerator*solve_numerator(n-i)%Mod;
save=numerator;
denominator=denominator*solve_denominator(cnt[a[i]])%Mod;
update(rank[i],1);
int ssum=query(rank[i]-1);
if(!ssum) continue; // 没有比当前位小的直接continue
numerator=numerator*solve_numerator(ssum)%Mod;
exgcd(denominator,Mod,x,y); // 求非共同质因子的逆元x
x=mymod(x%Mod+Mod);
for(rg j=1;j<=ptot;++j) res=res*qpow(p[j],cnt_numerator[j]-cnt_denominator[j])%Mod; // 消去共同质因子
res=res*numerator%Mod*x%Mod;
ans=mymod(ans+res);
back(ssum);numerator=save;
}
}
il void input(){
n=read(),Mod=read();
for(rg i=1;i<=n;++i) a[i]=read(),sa[i]=rank[i]=a[i];
std::sort(sa+1,sa+1+n);
len=std::unique(sa+1,sa+1+n)-(sa+1);
for(rg i=1;i<=n;++i) rank[i]=std::lower_bound(sa+1,sa+1+len,rank[i])-sa; // 离散化
int save=Mod;
for(rg i=2;i<=sqrt(Mod);++i){
if(Mod%i) continue;
p[++ptot]=i;
while(!(Mod%i)) Mod/=i;
}
if(Mod^1) p[++ptot]=Mod;
Mod=save;
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("PER.in","r",stdin);
#endif
input();
work();
printf("%lld\n",mymod(ans+1));
return 0;
}