题意
给出一个n个数的数列,每个数都是-1或是一个在1~K之间的数。-1表示这个位置可以填任意的数字。
求最少能有多少个逆序对。
(n<=10000 K<=100)
题解
bzoj双倍经验题 1831=1786
首先需要得到一个显然的结论,对于一个数列,如果我们交换一对逆序的元素,总逆序对数一点小于之前的总数。所以我们填的-1一定是不降的。
所以-1之间不会产生任何代价。
先O(n∗k∗log2n) 预处理出每个-1填不同数时产生的逆序对数。
然后设f[i][j]表示前i个-1,其中第i个天j的最小代价,然后O(n∗K)瞎DP即可。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=10005, maxw=105;
int n,m,K,ans,ans2,a[maxn],w[maxn][maxw],f[maxn][maxw],bit[2][maxw];
void Updata(int k,int x,int val){
for(;x<=K;x+=(x&(-x))) bit[k][x]+=val;
}
int Query(int k,int x){
int res=0;
for(;x;x-=(x&(-x))) res+=bit[k][x];
return res;
}
int main(){
scanf("%d%d",&n,&K);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
if(a[i]!=-1) ans2+=Query(1,K)-Query(1,a[i]),
Updata(1,a[i],1);
}
for(int i=1;i<=n;i++) if(a[i]==-1){
m++;
for(int j=1;j<=K;j++) w[m][j]=Query(0,K)-Query(0,j) + Query(1,j-1);
} else Updata(1,a[i],-1), Updata(0,a[i],1);
memset(f,63,sizeof(f)); f[0][1]=0;
for(int i=1;i<=m;i++)
for(int j=1;j<=K;j++)
for(int k=1;k<=j;k++) f[i][j]=min(f[i][j],f[i-1][k]+w[i][j]);
ans=1e+9;
for(int i=1;i<=K;i++) ans=min(ans,f[m][i]);
printf("%d\n",ans+ans2);
return 0;
}