题目链接:https://codeforces.com/contest/1525/problem/D
题目大意:
个数的0,1序列
,a[i]=0或1。1的个数一定不超过n的一半。我们需要把所有的1和某个0配对,一个0只能和一个1配对,若
和
配对,则会消耗
的资源。求所有1完成配对后,总资源消耗最少是多少?
题解:
个人的解法:
首先我们可以想一个朴素的贪心匹配方法:我们从前往后扫描,若当前扫描到的数为1,则与目前扫描过的未匹配过的最近的0匹配;若目前扫描到0,则与目前扫描过的未匹配过的1匹配。我们可以用两个栈分别维护未匹配过的0和1。
按照这种贪心方法,某些情况下是可以求出最优解的,例如:[1 1 1 0 0 0],[1 0 1 0 0 1]等
但是,在这些情况下是无法求出最优解的,例如:[0 0 0 1 1 1 0]
我们观察[0 0 0 1 1 1 0]的最优解,可以拆解为[0 0 0 1 1]和[1 0]两个序列,这两个子序列可以由上述方法求出最优解,两个子序列的最优解和在一起就是原序列的最优解。
我们求某个序列的最优解,可以把它分解为若干个不相交的连续子序列,这些子序列可以按照朴素的贪心匹配方法求出最优解。
有了这个结论,我们就可以进行动态规划。
表示前
个数匹配好的最优解,转移就是枚举以
结束的子序列,我们设
表示序列
到
朴素匹配法的解,则转移方程式为:
初始化:
状态数,转移
,复杂度
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int nn =5100;
const int inff = 0x3fffffff;
const double eps = 1e-8;
typedef long long LL;
const double pi = acos(-1.0);
const LL mod = 1000000007;
int n;
int a[nn];
int dp[nn];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
dp[i]=inff;
}
dp[0]=0;
for(int i=1;i<=n;i++)
{
int num=0;
stack<int>one,zero;
int cur=0;
for(int j=i;j>=1;j--)
{
if(a[j]==1)
num++;
if(a[j]==1)
{
if(zero.size())
{
int u=zero.top();
zero.pop();
cur+=u-j;
} else {
one.push(j);
}
} else {
if(one.size())
{
int u=one.top();
one.pop();
cur+=u-j;
} else {
zero.push(j);
}
}
if(one.size())
continue;
if(dp[j-1]!=inff)
{
dp[i]=min(dp[i],dp[j-1]+cur);
}
}
//cout<<dp[i]<<endl;
}
cout<<dp[n]<<endl;
return 0;
}
官方解法:
我们设最终匹配好后数字1的坐标从小打到为,数字0的坐标为
.
那么不难证明,最优的匹配方式一定是。也就是最小的x匹配最小的y,依此类推。
有了这个结论我们就可以进行动态规划:
表示,用了前i个1和前j个0进行匹配的最优解。
转移有两种策略:
1,第j个0和第i个1进行匹配,,
表示x的绝对值
2,第j个0不和第i个1匹配,
复杂度
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int nn =5100;
const int inff = 0x3fffffff;
const double eps = 1e-8;
typedef long long LL;
const double pi = acos(-1.0);
const LL mod = 1000000007;
int n;
int a[nn];
int dp[nn][nn];
int main()
{
scanf("%d",&n);
vector<int>one,zero;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(a[i]==1)
one.push_back(i);
else
zero.push_back(i);
}
int oneN=one.size();
int zeroN=zero.size();
for(int i=0;i<=oneN;i++)
{
for(int j=0;j<=zeroN;j++)
{
dp[i][j]=inff;
}
}
for(int i=0;i<=zeroN;i++)
dp[0][i]=0;
for(int i=1;i<=oneN;i++)
{
for(int j=i;j<=zeroN;j++)
{
dp[i][j]=min(dp[i][j-1],dp[i-1][j-1]+abs(one[i-1]-zero[j-1]));
}
}
cout<<dp[oneN][zeroN]<<endl;
return 0;
}