【题目链接】
【解题思路】
这道题我本来是想用贪心,用sort从大到小排序后,减去两张骨牌的差直到最小,结果却发现自己WA了。
很显然我的贪心是错误的,于是我后来就用了老师教的
d
p
dp
dp (其实贪心也是可以的)。
我们首先把它当作一道判定性的问题来思考发现其实并不难。
定义
f
i
,
j
f_{i,j}
fi,j 代表前
i
i
i 张骨牌能否凑成差为
j
j
j,当自己辛辛苦苦码好了代码,却发现问题问的是最少步骤。
不过也没有太大的关系,我们将判定的是否存在的
t
r
u
e
true
true 和
f
a
l
s
e
false
false 替换成差为
j
j
j 的最少步骤就行了。
输出的时候再循环找一个差最小的最少步骤输出即可。
状态转移方程
d
i
,
j
=
m
i
n
(
d
i
−
1
,
k
−
a
i
+
b
i
,
d
i
−
1
,
k
−
b
i
+
a
i
+
1
)
d_{i,j}=min(d_{i-1,k-a_i+b_i},d_{i-1,k-b_i+a_i}+1)
di,j=min(di−1,k−ai+bi,di−1,k−bi+ai+1)
最后我们还能做一个小优化,
d
p
dp
dp 时将差最大和最小的范围求出来,找的时候跟方便。
【CODE】
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
int n,maxn2,minn,maxn;
const int M=3501;//0的位置,因为有负数不能让数组越界
int a[1010];
int b[1010];
int d[1010][M*2];//正负各一半
int main ()
{
cin>>n;
for (int i=1;i<=n;i++)
{
cin>>a[i]>>b[i];
minn=minn-abs(a[i]-b[i]);//差的最小值
maxn=maxn+abs(a[i]-b[i]);//差的最大值
}
memset(d,127,sizeof(d));//因为要求最小值,初始化为最大值
maxn2=d[0][M];
d[0][M]=0;//前0张凑成0需要0步
for (int i=1;i<=n;i++)
for (int k=minn;k<=maxn;k++)
d[i][k+M]=min(d[i-1][k-a[i]+b[i]+M],d[i-1][k-b[i]+a[i]+M]+1);//状态转移
for (int i=0;i<=max(maxn,abs(minn));i++)
if (d[n][M-i]<maxn2||d[n][M+i]<maxn2)//绝对值正负两边都有一个值
{
cout<<min(d[n][M-i],d[n][M+i]);//取较小的哪一个输出
return 0;
}
return 0;
}