传送门
题意:有一个长度为n的0/1数列,1的个数小于等于0的个数,可以把i位置上的1移到j位置(如果j位置是0),i位置变成0,j位置变成1,代价是|i-j|
,问使得所有原先是1的位置都变成0的代价最小是多少。
(3<=n<=5000)
分析:我们可以把1/0的下标都拿出来,放进数组pos1\pos0
里面,问题就变成了拿0去匹配1,求最小匹配代价。
-
dp[i][j]
表示前i个1与前j个0完成了匹配(那么第i个1肯定是匹配了第j个0的)的最小代价 ,不合法的状态(比如前4个1与前3个0完成匹配)设为INF - 初始化dp为INF,dp[0][i]=0
- 转移:
dp[i][j]
=min(dp[i-1][k])
+|pos1[i]-pos1[j]| k∈[0,j-1]
- 这样的话是O(n³),但是我们可以开一个数组记录前缀最小值,优化成O(n²)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
const int maxn = 5e3+10;
const int mx = 40;
const int mod = 1e9+7;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const int INF = 1e9+7;
int n;
int a[maxn];
ll dp[maxn][maxn];//前i个1 匹配前j个0 i和j匹配 代价
ll mmin[maxn][maxn];
vector<int> pos0,pos1;
int main()
{
scanf("%d",&n);
pos0.push_back(0),pos1.push_back(0);
for(int i=1;i<=n;i++)
{
scanf("%d",a+i);
if(a[i]) pos1.push_back(i);
else pos0.push_back(i);
}
if(pos1.empty())
{
puts("0");
return 0;
}
ll ans=INF;
int n0=pos0.size()-1,n1=pos1.size()-1;
memset(mmin,0x3f3f3f,sizeof mmin);
memset(dp,0x3f3f3f,sizeof dp);
for(int i=0;i<=n0;i++) dp[0][i]=mmin[0][i]=0;
for(int i=1;i<=n1;i++)
{
for(int j=1;j<=n0;j++)
{
dp[i][j]=mmin[i-1][j-1]+abs(pos1[i]-pos0[j]);
}
for(int j=1;j<=n0;j++)
{
mmin[i][j]=min(dp[i][j],mmin[i][j-1]);
}
}
for(int i=1;i<=n0;i++)
{
ans=min(ans,dp[n1][i]);
}
cout<<ans;
return 0;
}