原题链接
P1282
题目类型:
普
及
+
/
提
高
{\color{lightgreen} 普及+/提高}
普及+/提高
AC记录:Accepted
题目大意
多米诺骨牌有上下2个方块组成,每个方块中有1~6个点。现有排成行的一堆多米诺骨牌,你可以在其中任意选择多个多米诺骨牌旋转 180 ° 180° 180°,让所有多米诺骨牌中上方块的和与下方块的和的差的绝对值最小。求最少的旋转次数是多少。
输入格式
输入文件的第一行是一个正整数 n n n,表示多米诺骨牌数。接下来的 n n n行表示 n n n个多米诺骨牌的点数。每行有两个用空格隔开的正整数,表示多米诺骨牌上下方块中的点数 a a a和 b b b。
输出格式
输出文件仅一行,包含一个整数。表示求得的最小旋转次数。
S a m p l e \mathbf{Sample} Sample I n p u t \mathbf{Input} Input
4
6 1
1 5
1 3
1 2
S a m p l e \mathbf{Sample} Sample O u t p u t \mathbf{Output} Output
1
H
i
n
t
&
E
x
p
l
a
i
n
\mathbf{Hint\&Explain}
Hint&Explain
题目中所示骨牌如下:
只要转动最后一个骨牌,所有多米诺骨牌中上方块的和与下方块的和的差的绝对值就为|(6+1+1+2)-(1+5+3+1)|=|10-10|=0
,为最小值,总共转动1
次。
数据范围
对于 100 % 100\% 100%的数据满足: 1 ≤ n ≤ 1000 , 1 ≤ a , b ≤ 6 1≤n≤1000,1\le a,b\le 6 1≤n≤1000,1≤a,b≤6。
解题思路
d
p
dp
dp,妥妥的
d
p
dp
dp。
我们可以设
f
i
,
j
f_{i,j}
fi,j为前
i
i
i个骨牌,能不能构成差值为
j
j
j的情况。如果能,保存最少转动次数,否则值为0x7f7f7f7f ( 2139062143 )
。
枚举每一个可能的差值,然后计算他如果加上这一个多米诺骨牌的点数后在不在
n
,
m
n,m
n,m最大时差值的范围内,然后取最小值。
看文字好像不太懂,还是看动态转移方程吧。
设
u
i
u_i
ui为第
i
i
i个多米诺骨牌的上方块的点数,
d
i
d_i
di为第
i
i
i个多米诺骨牌的下方块的点数,其余同上。则:
f
i
,
j
=
{
f
i
−
1
,
j
−
(
u
i
−
d
i
)
−
6000
≤
j
−
(
u
i
−
d
i
)
≤
6000
f
i
−
1
,
j
−
(
d
i
−
u
i
)
+
1
−
6000
≤
j
−
(
d
i
−
u
i
)
≤
6000
m
i
n
(
f
i
−
1
,
j
−
(
u
i
−
d
i
)
,
f
i
−
1
,
j
−
(
d
i
−
u
i
)
)
同
时
满
足
前
两
个
条
件
f_{i,j}=\begin{cases} f_{i-1,j-(u_i-d_i)} & -6000\le j-(u_i-d_i)\le 6000 \\ f_{i-1,j-(d_i-u_i)}+1 & -6000\le j-(d_i-u_i)\le 6000 \\ min\left(f_{i-1,j-(u_i-d_i)},f_{i-1,j-(d_i-u_i)}\right) & 同时满足前两个条件 \\ \end{cases}
fi,j=⎩⎪⎨⎪⎧fi−1,j−(ui−di)fi−1,j−(di−ui)+1min(fi−1,j−(ui−di),fi−1,j−(di−ui))−6000≤j−(ui−di)≤6000−6000≤j−(di−ui)≤6000同时满足前两个条件
第一个条件就是直接加入这一个多米诺骨牌。
第二个条件就是把这个多米诺骨牌旋转
180
°
180°
180°后再加入。
第三个条件不用解释。
注意:
1.由于是从上一个状态转移过来,所以在转移时是用 j − ( u i − d i ) j-(u_i-d_i) j−(ui−di),而不是 j + ( u i − d i ) j+(u_i-d_i) j+(ui−di)。 j − ( d i − u i ) j-(d_i-u_i) j−(di−ui)同理。
2.在处理差值的时候有可能会出现负数,而数组下标不能是负数,所以要把整个数组平移,我这里用的是平移 1000 × 6 = 6000 1000\times6=6000 1000×6=6000位,存在 b a s e base base里。
而答案是什么呢?
由于他要求的是最小差值,所以就涉及到了绝对值。因为你可以上面比下面大3
,或者下面比上面大3
,他们的差值都是3
。所以要求
−
x
-x
−x和
x
x
x中的最小值。而确定答案就枚举差值,找到第一个
f
n
,
b
a
s
e
−
i
f_{n,base-i}
fn,base−i或
f
n
,
b
a
s
e
+
i
f_{n,base+i}
fn,base+i小于0x7f7f7f7f
的差值就可以了。
优化
在循环差值的时候,原始算法循环的是
−
6000
⋯
6000
-6000\cdots 6000
−6000⋯6000,最多循环1,000*12,000=12,000,000
次,费时间。
那么最大的和最小的差值是什么呢?
首先,如果前面的多米诺骨牌都是上
6
6
6下
0
0
0,那么差值为
6
i
6i
6i,反之,差值为
−
6
i
-6i
−6i。而这一多米诺骨牌的差值,就是
u
i
−
d
i
u_i-d_i
ui−di。所以差值我们只要循环
[
−
6
i
−
(
u
i
−
d
i
)
,
6
i
+
(
u
i
−
d
i
)
]
[-6i-(u_i-d_i),6i+(u_i-d_i)]
[−6i−(ui−di),6i+(ui−di)]就可以了。
最后,祝大家早日
上代码
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
const int base=6000;
int f[1010][12001];
int n;
int u[1010],d[1010];
int main()
{
// cout<<sizeof(f)/8.0/1024.0<<" KB"<<endl;
cin>>n;
for(int i=1; i<=n; i++)
cin>>u[i]>>d[i];
memset(f,0x7f,sizeof(f));
f[0][base]=0;
for(int i=1; i<=n; i++)
for(int k=i*(-6)-abs(u[i]-d[i])+base; k<=i*6+abs(u[i]-d[i])+base; k++)
{
if(k-(u[i]-d[i])>=0&&k-(u[i]-d[i])<=12000)
f[i][k]=min(f[i][k],f[i-1][k-(u[i]-d[i])]);
if(k-(d[i]-u[i])>=0&&k-(d[i]-u[i])<=12000)
f[i][k]=min(f[i][k],f[i-1][k-(d[i]-u[i])]+1);
}
for(int i=0; ; i++)
{
if(f[n][base-i]<0x7f7f7f7f||f[n][base+i]<0x7f7f7f7f)
{
// cout<<i<<endl;
// cout<<f[n][base-i]<<" "<<f[n][base+i]<<endl;
cout<<min(f[n][base-i],f[n][base+i])<<endl;
break;
}
}
return 0;
}
完美切题 ∼ \sim ∼