【洛谷P4280】逆序对
题目
题目链接:https://www.luogu.com.cn/problem/P4280
暑假到了,小可可和伙伴们来到海边度假,距离海滩不远的地方有个小岛,叫做欢乐岛,整个岛是一个大游乐园,里面有很多很好玩的益智游戏。碰巧岛上正在举行“解谜题赢取免费门票”的活动,只要猜出来迷题,那么小可可和他的朋友就能在欢乐岛上免费游玩两天。
迷题是这样的:给出一串全部是正整数的数字,这些正整数都在一个范围内选取,谁能最快求出这串数字中“逆序对”的个数,那么大奖就是他的啦!
当然、主办方不可能就这么简单的让迷题被解开,数字串都是被处理过的,一部分数字被故意隐藏起来,这些数字均用-1来代替,想要获得大奖就必须求出被处理的数字串中最少能有多少个逆序对。小可可很想获得免费游玩游乐园的机会,你能帮助他吗?
注:“逆序对”就是如果有两个数A和B,A在B左边且A大于B,我们就称这两个数为一个“逆序对”,例如:4 2 1 3 3里面包含了5个逆序对:(4, 2)、(4, 1)、(4, 3)、(4, 3)、(2, 1)。
\(n\leq 10000,m\leq 100\)。
思路
不难发现填进去的数一定是单调不减的,手画分类讨论一下就可以得出。
然后就是一道裸的 dp 了。设 \(f_{i,j}\) 表示前 \(i\) 个位置,最后一个填的是 \(j\) 的最小逆序对数。注意这里只计算填入的数产生的逆序对数量,已经有的数可以直接计算出来最后加上。
预处理 \(g_{0/1,i,j}\) 表示前缀不小于 \(j\) 的数字数量,以及后缀不大于 \(j\) 的数字数量。然后转移就直接随便写写就行了。
时间复杂度 \(O(nm)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=10010,M=110,Inf=1e9;
int n,m,cnt,a[N],f[N][M],g[2][N][M];
int main()
{
scanf("%d%d",&n,&m);
memset(f,0x3f3f3f3f,sizeof(f));
f[0][1]=0;
for (int i=1;i<=n;i++)
scanf("%d",&a[i]);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
g[0][i][j]=g[0][i-1][j]+(a[i]>=j)*(a[i]!=-1);
for (int i=n;i>=1;i--)
for (int j=1;j<=m;j++)
g[1][i][j]=g[1][i+1][j]+(a[i]<=j)*(a[i]!=-1);
for (int i=1;i<=n;i++)
{
if (a[i]!=-1)
{
memcpy(f[i],f[i-1],sizeof(f[i]));
cnt+=g[0][i-1][a[i]+1];
}
else
{
int minn=Inf;
for (int j=1;j<=m;j++)
{
minn=min(minn,f[i-1][j]);
f[i][j]=minn+g[0][i-1][j+1]+g[1][i+1][j-1];
}
}
}
int ans=Inf;
for (int i=1;i<=m;i++)
ans=min(ans,f[n][i]);
printf("%d",ans+cnt);
return 0;
}