题解: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\) 代表暂未填数):

\[\begin{pmatrix} \binom{n-2}{2}&1&-1&0&0&-1&0\\ 1&\binom{n-2}{2}&0&-1&-1&0&0\\ 1&0&\color{red}{t}&1&1&0&-1\\ 0&1&1&\color{red}{t}&0&1&-1\\ 0&1&1&0&\color{red}{t}&1&-1\\ 1&0&0&1&1&\color{red}{t}&-1\\ 0&0&1&1&1&1&\binom{n}{2}-4\\ \end{pmatrix}\]

从左到右、从上到下均按前文的顺序:\((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\),矩阵如下:

\[\begin{pmatrix} \binom{n-2}{2}&1&n-2&0&0&n-2&0\\ 1&\binom{n-2}{2}&0&n-2&n-2&0&0\\ 1&0&\color{red}{t}&1&1&0&n-3\\ 0&1&1&\color{red}{t}&0&1&n-3\\ 0&1&1&0&\color{red}{t}&1&n-3\\ 1&0&0&1&1&\color{red}{t}&n-3\\ 0&0&1&1&1&1&\binom{n}{2}-4\\ \end{pmatrix}\]

从左到右、从上到下均按前文的顺序:\((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;
} 
posted @ 2026-06-05 09:36  Wxb2010  阅读(13)  评论(0)    收藏  举报