「AGC023E」Inversions【組合計數】
阿新 • • 發佈:2021-07-06
一道經典的組合計數題目。
從小到大依次確定每個 \(p_i\) 的取值,此時前 \(i-1\) 個 \(p\) 都在 \(p_i\) 的取值範圍中,不能選,因此可選的只有 \(c_i-i+1\) 個。
:考慮使用總數減去順序對的方案數:
\[ans_{i,j}=f(n)-\dfrac{f(n)\frac{a_j-b_j}{a_i-b_i+1}\prod_{k=b_j+1}^{b_i-1}\frac{c_k-k}{c_k-k+1}}{2}
\] 的 \(\dfrac{a_j-b_j}{T_{b_j}}\),然後按 \(a_i\) 從小到大加入線段樹,加入 \(i\) 前計算其貢獻,就可以容易通過線段樹區間求和完成了。複雜度 \(\mathcal O(n\log n)\)。
AGC023E
Description
給定一個長度為 \(n\) 的序列\(a\),求所有滿足 \(\forall i,P_i\le a_i\) 的\(1\sim n\) 的排列的逆序數的和。
答案對 \(10^9+7\) 取模。
\(n\le 2\times 10^5,1\le a_i\le n\)。
Solution
首先,考慮總方案,將 \(a\) 從大到小排序得到序列 \(c\) ,記 \(b_i\) 為 \(a_i\) 的排名,那麼合法的排列方案有:
\[f(n)=\prod_{i=1}^{n}(a_i-b_i+1)=\prod_{i=1}^{n}(c_i-i+1) \]這是因為按照 \(a_i\)
考慮分別計算每一對數的貢獻。
對於 \(i>j,a_i>a_j\),此時 \(a_i\) 中 \(>a_j\) 的部分可以忽略掉了,因此可以令 \(a_i\) 與 \(a_j\) 相同,此時的所有排列中一定恰好有一半滿足 \(p_j>p_j\),於是用總方案數除 \(2\) 即可。
\[ans_{i,j}=\dfrac{f(n)\frac{a_j-b_j}{a_i-b_i+1}\prod_{k=b_j+1}^{b_i-1}\frac{c_k-k}{c_k-k+1}}{2} \]對於 \(i<j,a_i> a_j\)
令 \(T(k)=\prod_{i=1}^{k}\dfrac{c_i-i}{c_i-i+1}\),暴力求解即可做到 \(\mathcal O(n^2)\)。
兩種不同的情況處理起來非常麻煩,考慮取出相似的部分:
\[\begin{aligned} g(i,j)&=\dfrac{f(n)\frac{a_j-b_j}{a_i-b_i+1}\prod_{k=b_j+1}^{b_i-1}\frac{c_k-k}{c_k-k+1}}{2}\\ &=\dfrac{f(n)T_{b_i-1}}{2(a_i-b_i+1)}\dfrac{a_j-b_j}{T_{b_j}}\\ \end{aligned}\\ ans_i=\sum_{j}[a_j<a_i]\{[i>j]g(i,j)+[i<j](f(n)-g(i,j)\} \]於是考慮用線段樹維護每個 \(j\)
你直接照著這個式子做,會發現始終 \(WA\) 了幾個點,問題出在一開始將區間積轉化為字首積的部分:當某個 \(i\) 滿足 \(c_i-i=0\) 時,在計算 \(\prod_{k=l}^{r}\) 且 \(l>i\) 時,\(c_i-i\) 會成為除數,導致原式結果出錯。解決方法是
\[g(i,j)=\dfrac{f(n)}{2(a_i-b_i+1)}(a_j-b_j)\prod_{k=b_j+1}^{b_i-1}\dfrac{c_k-k}{c_k-k+1}\\ \]讓每個 \(i\) 加入線段樹時,將全域性的值都乘一個 \(\dfrac{c_{b_i}-b_i}{c_{b_i}-b_i+1}\),並將加入點的初值設為 \(a_i-b_i\) 就能完美解決這一問題了。
Code
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int mod=1e9+7;
const int N=2e5+10;
inline int add(int x,int y){return (x+y>=mod)?x+y-mod:x+y;}
inline void inc(int &x,int y){x=add(x,y);}
inline int dec(int x,int y){return (x-y<0)?x-y+mod:x-y;}
inline int ksm(int x,int y){
int ret=1;
for(;y;y>>=1,x=1ll*x*x%mod) if(y&1) ret=1ll*ret*x%mod;
return ret;
}
int n,a[N],b[N],c[N],id[N],f;
inline bool cmp(int x,int y){return (a[x]^a[y])?a[x]<a[y]:x<y;}
namespace SGT{
#define lc (p<<1)
#define rc (p<<1|1)
#define mid ((l+r)>>1)
pii tr[N<<2];int tag[N<<2];
inline pii operator *(const pii &x,const pii &y){
return make_pair(x.first+y.first,add(x.second,y.second));
}
inline pii operator *(const pii &x,const int &y){
return make_pair(x.first,1ll*x.second*y%mod);
}
inline void pushdown(int p){
if(tag[p]==1) return;
tag[lc]=1ll*tag[lc]*tag[p]%mod;tr[lc]=tr[lc]*tag[p];
tag[rc]=1ll*tag[rc]*tag[p]%mod;tr[rc]=tr[rc]*tag[p];
tag[p]=1;
}
inline pii query(int p,int ql,int qr,int l=1,int r=n){
if(ql>qr) return make_pair(0,0);
if(ql<=l&&r<=qr) return tr[p];
pushdown(p);
pii ret=make_pair(0,0);
if(ql<=mid) ret=ret*query(lc,ql,qr,l,mid);
if(qr>mid) ret=ret*query(rc,ql,qr,mid+1,r);
return ret;
}
inline void update(int p,int x,int v,int l=1,int r=n){
if(l==r){tr[p]=make_pair(1,v);return ;}
pushdown(p);
if(x<=mid) update(lc,x,v,l,mid);
else update(rc,x,v,mid+1,r);
tr[p]=tr[lc]*tr[rc];
}
inline void build(int p,int l=1,int r=n){
tag[p]=1;
if(l==r) return ;
build(lc,l,mid);build(rc,mid+1,r);
}
}
using namespace SGT;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]),id[i]=i;
sort(id+1,id+n+1,cmp);
f=1;
for(int i=1;i<=n;++i){
b[id[i]]=i,c[i]=a[id[i]];
if(c[i]-i+1<=0){puts("0");return 0;}
f=1ll*f*(c[i]-i+1)%mod;
}
int ans=0;
build(1);
for(int u=1;u<=n;++u){
int i=id[u];
pii p=query(1,1,i-1),q=query(1,i+1,n);
int t=1ll*f*ksm(2ll*(dec(a[i],b[i])+1)%mod,mod-2)%mod;
inc(ans,1ll*t*p.second%mod);
inc(ans,dec(1ll*q.first*f%mod,1ll*q.second*t%mod));
int v=1ll*(a[i]-b[i])*ksm(a[i]-b[i]+1,mod-2)%mod;
tag[1]=1ll*tag[1]*v%mod;
tr[1]=tr[1]*v;
update(1,i,dec(a[i],b[i]));
}
printf("%d\n",ans);
return 0;
}