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; }