P1282 多米诺骨牌


原题链接

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 1n1000,1a,b6

解题思路

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=fi1,j(uidi)fi1,j(diui)+1min(fi1,j(uidi),fi1,j(diui))6000j(uidi)60006000j(diui)6000
第一个条件就是直接加入这一个多米诺骨牌。
第二个条件就是把这个多米诺骨牌旋转 180 ° 180° 180°后再加入。
第三个条件不用解释。

注意:

1.由于是从上一个状态转移过来,所以在转移时是用 j − ( u i − d i ) j-(u_i-d_i) j(uidi),而不是 j + ( u i − d i ) j+(u_i-d_i) j+(uidi) j − ( d i − u i ) j-(d_i-u_i) j(diui)同理。
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,basei f n , b a s e + i f_{n,base+i} fn,base+i小于0x7f7f7f7f的差值就可以了。


优化

在循环差值的时候,原始算法循环的是 − 6000 ⋯ 6000 -6000\cdots 6000 60006000,最多循环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 uidi。所以差值我们只要循环 [ − 6 i − ( u i − d i ) , 6 i + ( u i − d i ) ] [-6i-(u_i-d_i),6i+(u_i-d_i)] [6i(uidi),6i+(uidi)]就可以了。


最后,祝大家早日
请添加图片描述

上代码

#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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值