BZOJ1786 [Ahoi2008]Pair 配对 动态规划 逆序对

欢迎访问~原文出处——博客园-zhouzhendong

去博客园看该题解


题目传送门 - BZOJ1786


题意概括

  给出长度为n的数列,只会出现1~k这些正整数。现在有些数写成了-1,这些-1可以变成任何数。

  求把这些-1变成1~k中的正整数之后,最少的逆序对个数为多少。


 

题解

  我们可以判断,这些-1中写的数字一定是单调不降的。

  为什么?我们把答案序列的所有-1位抽出来,如果答案序列中有一组是逆序的,那么交换他们,一定可以保证小的那个换到大的那个的位置的时候,它左右产生的逆序对数一定比大的原先不多,同理,大的在原先的小的的位置上,产生的逆序对数也比原来小的不多。交换他们,本身就会减少一个逆序对,那么一定是更优的。

  所以我们确定了一定是单调不降的。

  那么这些-1位之间也不会有逆序对。

  那么就是一个简单的dp了。

  设dp[i][j]表示前i位,填到了j这个数,最少的逆序对数。注意这里的逆序对数是指-1中的数添入之后产生的逆序对数。

  然后取一个最小值,作为所有-1位产生的最少逆序对数。

  然后加上原来有的数产生的逆序对数就可以了。


 

代码

#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cmath>
using namespace std;
const int N=10000+5,M=100+5;
int n,m,a[N],sml[N][M],big[N][M],tot[N][M],rot[N][M],dp[N][M];
int main(){
	scanf("%d%d",&n,&m);
	memset(tot,0,sizeof tot);
	memset(rot,0,sizeof rot);
	memset(sml,0,sizeof sml);
	memset(big,0,sizeof big);
	memset(dp,0,sizeof dp);
	for (int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		for (int j=1;j<=m;j++)
			tot[i][j]=tot[i-1][j];
		if (a[i]!=-1)
			tot[i][a[i]]++;
		for (int j=m;j>=1;j--)
			big[i][j]=big[i][j+1]+tot[i][j];
	}
	for (int i=n;i>=1;i--){
		for (int j=1;j<=m;j++)
			rot[i][j]=rot[i+1][j];
		if (a[i]!=-1)
			rot[i][a[i]]++;
		for (int j=1;j<=m;j++)
			sml[i][j]=sml[i][j-1]+rot[i][j];
	}
	int tot=0;
	for (int i=1;i<=n;i++){
		int Min=1<<28;
		if (a[i]!=-1)
			tot+=big[i-1][a[i]+1]+sml[i+1][a[i]-1];
		for (int j=1;j<=m;j++){
			if (a[i]!=-1){
				dp[i][j]=dp[i-1][j];
				continue;
			}
			Min=min(Min,dp[i-1][j]);
			dp[i][j]=Min+big[i-1][j+1]+sml[i+1][j-1];
		}
	} 
	int ans=1<<28;
	for (int i=1;i<=m;i++)
		ans=min(ans,dp[n][i]);
	printf("%d",ans+tot/2);
	return 0;
}

  

posted @ 2017-08-30 20:54  zzd233  阅读(323)  评论(0编辑  收藏  举报