codeforces 362C

题意:给出一个数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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值