ZigZagKmp
Think twice, code once.

题意简述

给定一个长度为 \(n\) 序列和 \(m\) 次操作,每次操作交换一对位置上的元素。

现在每一个操作可以做也可以不做,求所有操作方案得到的最终序列的逆序对个数和。

\(n,m\le 3000\)

算法分析

直接做没有头绪,考虑期望化。

期望有一个很重要的线性性质,可以将问题独立。我们考虑每一对逆序对的单元是一对数,因此我们需要维护所有二元组 \(\left(x,y\right)\) 满足 \(a_x<a_y\) 的概率。

这样对于一次交换操作的做或不做,只会影响到与当前操作相关的二元组,这些二元组要么包含第一个数,要么包含第二个数,总量为 \(O(n)\) ,可以接受。

注意最后将概率和乘上 \(2^m\)

总时间复杂度为 \(O(n^2+nm)\)

代码实现

#include<bits/stdc++.h>
using namespace std;
#define maxn 3005
#define maxm 2000005
#define inf 0x3f3f3f3f
#define int long long
#define mod 1000000007
#define local
void file(string s){freopen((s+".in").c_str(),"r",stdin);freopen((s+".out").c_str(),"w",stdout);}
template <typename Tp> void read(Tp &x){
	int fh=1;char c=getchar();x=0;
	while(c>'9'||c<'0'){if(c=='-'){fh=-1;}c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c&15);c=getchar();}x*=fh;
}
int n,m;
int a[maxn];
int dp[maxn][maxn];
int ksm(int b,int p){int ret=1;while(p){if(p&1)ret=1ll*ret*b%mod;b=1ll*b*b%mod;p>>=1;}return ret;}
signed main(){
	read(n);read(m);
	for(int i=1;i<=n;++i)read(a[i]);
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j){
			dp[i][j]=(a[i]<a[j]);
		}
	}
	for(int i=1,x,y;i<=m;++i){
		read(x);read(y);
		dp[x][y]=dp[y][x]=1ll*ksm(2,mod-2)*(dp[x][y]+dp[y][x])%mod;//两者交换,等概率出现自身与对方的情况
		for(int j=1;j<=n;++j){
			if(j!=x&&j!=y){//下面两种同理
				dp[x][j]=dp[y][j]=1ll*ksm(2,mod-2)*(dp[x][j]+dp[y][j])%mod;
				dp[j][x]=dp[j][y]=1ll*ksm(2,mod-2)*(dp[j][x]+dp[j][y])%mod;
			}
		}
	}
	int ans=0;
	for(int i=1;i<=n;++i){
		for(int j=1;j<i;++j){
			ans=(ans+dp[i][j])%mod;
		}
	}
	ans=1ll*ans*ksm(2,m)%mod;
	printf("%lld\n",ans);
	return 0;
}
posted on 2021-02-05 22:31  ZigZagKmp  阅读(118)  评论(0编辑  收藏  举报