题解:P4223 期望逆序对
前言
同步自洛谷专栏,题目传送门。
基本认识:矩阵能够表示一次转移。
弱化版练习:排列交换。
解题思路
矩阵的构造与递推
事实上,这种题目有一定的套路:
对于一对下标(即在序列中的位置),记原来一对数为 \((A,B)\),不同于 \(A,B\) 的数均认为是 \(C\),
则这对下标最终对应的数有七种可能(下文称二元组):\((A,B)\),\((B,A)\),\((A,C)\),\((C,A)\),\((B,C)\),\((C,B)\),\((C,C)\)。
$\red{\text{合理性说明}}$
为什么这七个二元组可以描述所有情况?注意有这样一个性质:对于一对下标,除原来一对数以外的所有数,被交换到这对下标上的概率是相等的,所以可以用 \(C\) 指代。
在本文中,称前两种为全同组,最后一种为异组,中间的四种为半同组。(仅为了方便叙述)
然后我们按上述顺序定义矩阵:
转移矩阵 \(S\) 的第 \(i\) 行第 \(j\) 列代表从第 \(i\) 种二元组向第 \(j\) 种二元组转移的方案数。
主对角线
开始填转移矩阵,先考虑主对角线上的,
全同组中,自己向自己转移的方案数为不涉及组内两数的方案数,即在两数之外选择,方案数为 \(\binom{n-2}{2}\);
半同组中,自己向自己转移的方案数相较于全同组,还可以将 \(C\) 与二元组外的不等于 \(A,B\) 的数交换,有 \(n-3\) 种,方案数 \(\binom{n-2}{2}+n-3\);
异组中,只要二元组中的数不将 \(A,B\) 换回均可,简单容斥一下,方案数 \(\binom{n}{2}-4\)。
二元组组内交换
主对角线上填完后,再来考虑一些特殊情况,
如果两个二元组的组成相同(\(A,B,C\) 对应个数相等),只是顺序不同,那么它们之间的转移的方案是唯一的,方案数 \(1\)。
不存在转移方案
如果两个二元组之间,没有任何一个对应位置上的数相等,且不属于二元组组内交换,那么它们之间不可能只转移一次,方案数为 \(0\)。
其它唯一的转移
考虑还有哪些转移是唯一的呢?
如果我们将 \(A,B\) 中的某一个从二元组外换入二元组内一个确定的位置,那么方案数也是 \(1\)。
$\red{\text{目前的矩阵}}$
为了避免矩阵太宽需要滑动,令 \(t=\binom{n-2}{2}+n-3\),并上色。
矩阵如下(\(-1\) 代表暂未填数):
从左到右、从上到下均按前文的顺序:\((A,B)\),\((B,A)\),\((A,C)\),\((C,A)\),\((B,C)\),\((C,B)\),\((C,C)\)。
其它位置
观察剩下的未填数的部分,有两点结论:
-
可以分为从全同组到半同组、从半同组到异组。
-
都将组内的一个 \(A,B\) 换到了组外。
从全同组到半同组,由于(二元组)组外有 \(n-2\) 个 \(C\),方案数为 \(n-2\);
从半同组到异组,由于组内有一个 \(C\),组外还剩 \(n-3\) 个,方案数 \(n-3\)。
完整的转移矩阵
令 \(t=\binom{n-2}{2}+n-3\),矩阵如下:
从左到右、从上到下均按前文的顺序:\((A,B)\),\((B,A)\),\((A,C)\),\((C,A)\),\((B,C)\),\((C,B)\),\((C,C)\)。
$\red{\text{矩阵的检查}}$
如果担心矩阵填错,可以初步检查每一行所填的数的和是否等于 $\binom{n}{2}$,因为总的交换方案数等于 $\binom{n}{2}$。转移矩阵构造完后,为了符合转移矩阵定义,规定初始矩阵为行向量,并令第 \(0\) 项为 \(1\) 代表初始状态为 \((A,B)\)。
递推直接应用矩阵快速幂即可。
答案统计
记给出的排列的第 \(i\) 项为 \(A_i\)(下标从 \(1\) 开始)。
如果我们枚举每一对数,时间复杂度 \(O(n^2)\),无法接受,尝试挖掘性质。
考虑到:对于某一个确定的数及其位置,我们只需要知道它的前面(或后面)有多少数比它大(或小)即可。
即只需了解相对大小关系和相对位置关系,这是一个二维偏序问题,可以用树状数组快速求解。
枚举每一个 \(A_i\),设在 \(A_i\) 之前,有 \(d\) 个数小于 \(A_i\),\(u\) 个数大于 \(A_i\)。
认为当前的 \(A_i\) 对应二元组中的 \(B\),为了避免数字下标需要对应,直接使用二元组作为下标,
\((A,B)\):贡献为 \(u\times ans_{(A,B)}\)。
\((B,A)\):贡献为 \(d\times ans_{(B,A)}\)。
\((A,C)\) 和 \((C,A)\) 暂时待定。
\((B,C)\):贡献为 \(d\times ans_{(B,C)}\times \frac{A_i-1-1}{n-2}+u\times ans_{(B,C)}\times \frac{A_i-1}{n-2}\)。
$\red{\text{关于贡献值}}$
$u$ 项的 $\frac{A_i-1}{n-2}$ 是表示在所有的 $C$ 中有 $A_i-1$ 个满足条件;\(d\) 项额外减一是由于 \(d\) 指在 \(A_i\) 之前,有 \(d\) 个数小于 \(A_i\),相当于有一个小于 \(A_i\) 的数作为二元组中的 \(A\),需要删去。
\(A_i-1-1=-1\) 时,\(d=0\),无需特判防止错算。
\((C,B)\):贡献为 \(d\times ans_{(C,B)}\times \frac{n-A_i}{n-2}+u\times ans_{(C,B)}\times \frac{n-A_i-1}{n-2}\)。
\((C,C)\):只在全局考虑一次,贡献为 \(\frac{1}{2}\times \binom{n}{2}\times ans_{(C,C)}\),\(\binom{n}{2}\) 表示数对的总数,\(\frac{1}{2}\) 表示仅有一半是逆序对。
注:关于如何证明在不取模的情况下,\(\binom{n}{2}\times ans_{(C,C)}\) 一定能被 \(2\) 整除,蒟蒻也不清楚,有能够清楚证明的大佬望私信,感谢。
回到刚刚待定的待定的 \((A,C)\) 和 \((C,A)\),由于其中不存在 \(B\),不便计算,考虑换一种方式。
由于树状数组过程是对暴力枚举每一个数的优化,考虑基于暴力枚举思考,
可以发现:对于第 \(i\) 个数,它作为其它数的 \(A\) 共有 \(n-i\) 次。
可以将数组翻转,再用求解 \((B,C)\) 和 \((C,B)\) 的方法求解这两种情况,注意二元组下标不同。
时间复杂度
设 \(T=7\) 为矩阵大小,矩阵快速幂 \(O(T^3\log{k})\),树状数组 \(O(n\log{n})\),答案统计 \(O(Tn)\),瓶颈在树状数组,总时间复杂度 \(O(n\log{n})\)。
代码实现
在代码实现的过程中,\((A,C)\) 和 \((C,A)\) 的贡献可以直接利用 \(u\) 和 \(v\) 表示,避免翻转数组再次求解。
由于转移矩阵中出现了 \(n-3\) 等式子,注意特判 \(n\) 很小的情况。
$\red{\text{code}}$
#include<bits/stdc++.h>
using namespace std;
#define mod 1000000007
#define N 500005
int A[N],n,m;
//矩阵乘法中用到了 k,原题 k 对应 m
inline void add(int &a,int b){
a+=b,(a>=mod)&&(a-=mod);
}
struct matrix{
int a[7][7];
void init(){
for(int i=0;i<=6;i++){
for(int j=0;j<=6;j++) a[i][j]=0;
}
}
}ans,s;
matrix operator *(matrix &a,matrix &b){
matrix h;h.init();
for(int i=0;i<=6;i++){
for(int k=0;k<=6;k++){
//这样的枚举顺序内存访问连续,常数较小(尽管本题不卡常)
for(int j=0;j<=6;j++){
add(h.a[i][j],1ll*a.a[i][k]*b.a[k][j]%mod);
}
}
}
return h;
}
void matrix_ksm(int b){
for(;b;b>>=1,s=s*s){
if(b&1) ans=ans*s;
}
}
void pre_work(){//构造矩阵
ans.init(),s.init();
ans.a[0][0]=1;
int c=1ll*(n-3)*(n-2)/2%mod;
for(int i=0;i<=6;i++) s.a[i][i]=c;
for(int i=2;i<=6;i++) add(s.a[i][i],n-3);
add(s.a[6][6],n-4);
s.a[0][1]=1,s.a[0][2]=n-2,s.a[0][5]=n-2;
s.a[1][0]=1,s.a[1][3]=n-2,s.a[1][4]=n-2;
s.a[2][0]=1,s.a[2][3]=1,s.a[2][4]=1,s.a[2][6]=n-3;
s.a[3][1]=1,s.a[3][2]=1,s.a[3][5]=1,s.a[3][6]=n-3;
s.a[4][1]=1,s.a[4][2]=1,s.a[4][5]=1,s.a[4][6]=n-3;
s.a[5][0]=1,s.a[5][3]=1,s.a[5][4]=1,s.a[5][6]=n-3;
s.a[6][2]=1,s.a[6][3]=1,s.a[6][4]=1,s.a[6][5]=1;
}
class BIT{
private:
int tr[N]={};
inline int lowbit(int x){return x&(-x);}
public:
inline void add(int p){
for(;p<=n;p+=lowbit(p)) tr[p]++;
}
inline int query(int p){
int ret=0;
for(;p>=1;p-=lowbit(p)) ret+=tr[p];
return ret;
}
}T;
inline int ksm(int a,int b){
int ret=1;
for(;b;b>>=1,a=1ll*a*a%mod){
if(b&1) ret=1ll*ret*a%mod;
}
return ret;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&A[i]);
if(n==1){
puts("0");return 0;
}
else if(n==2){
if((A[1]<A[2])^(m&1)) puts("0");
else puts("1");
return 0;
}
pre_work(),matrix_ksm(m);
int tot=0,inv2=ksm(n-2,mod-2);
for(int i=1;i<=n;i++){
int d=T.query(A[i]),u=i-d-1;
//d->比它小的数的个数,u->比它大的数的个数
add(tot,1ll*ans.a[0][0]*u%mod);
add(tot,1ll*ans.a[0][1]*d%mod);
add(tot,1ll*ans.a[0][4]*d%mod*(A[i]-2)%mod*inv2%mod);
add(tot,1ll*ans.a[0][4]*u%mod*(A[i]-1)%mod*inv2%mod);
add(tot,1ll*ans.a[0][5]*u%mod*(n-A[i]-1)%mod*inv2%mod);
add(tot,1ll*ans.a[0][5]*d%mod*(n-A[i])%mod*inv2%mod);
//推导出后面有多少比它大/小的
u=n-A[i]-u,d=A[i]-1-d;
add(tot,1ll*ans.a[0][2]*d%mod*(A[i]-2)%mod*inv2%mod);
add(tot,1ll*ans.a[0][2]*u%mod*(A[i]-1)%mod*inv2%mod);
add(tot,1ll*ans.a[0][3]*u%mod*(n-A[i]-1)%mod*inv2%mod);
add(tot,1ll*ans.a[0][3]*d%mod*(n-A[i])%mod*inv2%mod);
T.add(A[i]);
}
add(tot,1ll*n*(n-1)/2%mod*((mod>>1)+1)%mod*ans.a[0][6]%mod);
printf("%d",tot);
return 0;
}

浙公网安备 33010602011771号