题意:给出一个数n,然后给出n个数,问交换其中某两个数所形成的新的数列得到的逆序数最少,输出最小逆序数并且输出有几种交换方式、
思路:看看题,n的最大只有5000,n^2的算法还是能挺过的、 我们发现交换两个元素a b实际上逆序数改变只会来源于a b之间的数, 以及 a b两个数本身,如果我们知道了a b两个数之间的数和a b形成的逆序数,那么我们就可以利用容斥原理算出交换后产生的逆序数。
详细解释在代码里面、
#include<bits/stdc++.h>
using namespace std;
const int qq =5e3+10;
int dp[qq][qq]; //dp[i][j]代表元素i,数组位于j(包括j)后的元素小于i的个数、
int num[qq];
int position[qq];
int main(){
int n;scanf("%d",&n);
for(int i=0; i<n; ++i){
scanf("%d",num+i);
position[num[i]] = i;
}
for(int i=0; i<n; ++i)
for(int j=n-1; j>=0; --j){ //从后向前算,满足递推式
if(num[j]<i) dp[i][j]++; //统计元素i大于数组j位置(包括j)后的元素小于i的个数、
dp[i][j]+=dp[i][j+1];
}
int sum = 0; //求逆序数的总数、
for(int i=0; i<n; ++i)
sum+=dp[i][position[i]];
int maxn = sum;
int k=1;
for(int j,i=0; i<n; ++i)
for(j=0; j<i; ++j){ //枚举交换第i个元素和第j个元素(j<i)
int aa = dp[num[i]][j+1]-dp[num[i]][i];//代表从i到j (j, i)比num[i]小的数的个数、
int dd = dp[num[j]][j+1]-dp[num[j]][i];//代表从i到j (j, i)比num[j]小的数的个数、
int cc = (i-j-1)-dd; //代表从i到j比num[j]大的数的个数、
int ns; // 特判一下num[i]和num[j]的大小 看他们是否形成逆序对、(因为我们统计了区间(j, i)对num[j],num[i]的贡献)
if(num[i]>num[j]) ns = sum+aa+cc+1-(2*(i-j-1)-aa-cc); //aa+bb+1 是交换后区间(j, i)对num[i], num[j]产生的逆序对、
else ns = sum+aa+cc-(2*(i-j-1)-aa-cc+1); //利用容斥原理、后面那一部分就是求之前区间(j, i)对num[j], num[i]产生的逆序对、
if(ns<maxn){
maxn = ns;
k = 1;
}else if(maxn==ns) ++k;
}
printf("%d %d\n", maxn, k);
return 0;
}