洛谷Oj-多米诺骨牌-背包

来源:互联网 发布:刻录软件找不到刻录机 编辑:程序博客网 时间:2024/05/20 06:07

问题描述:
多米诺骨牌有上下2个方块组成,每个方块中有1~6个点。现有排成行的

上方块中点数之和记为S1,下方块中点数之和记为S2,它们的差为|S1-S2|。例如在图8-1中,S1=6+1+1+1=9,S2=1+5+3+2=11,|S1-S2|=2。每个多米诺骨牌可以旋转180°,使得上下两个方块互换位置。 编程用最少的旋转次数使多米诺骨牌上下2行点数之差达到最小。

对于图中的例子,只要将最后一个多米诺骨牌旋转180°,可使上下2行点数之差为0。
AC代码:

int up[1010],down[1010],dp[1010][13000];//上方点数,下方点数,dp[i][j]代表前i张骨牌的上下点数之差为j(不是绝对值)的最小操作数,无解则为infint main(){    int n;    cin >> n;//骨牌的个数    for(int i = 1; i <= n; ++i)        scanf("%d%d",&up[i],&down[i]);//输入    memset(dp,0x7f,sizeof(dp));//因为是求最小的操作数,所以要初始化为无穷大,不能初始化为0    dp[0][6000] = 0;//边界,使前0个骨牌的上下点数之差为0的操作数为0    for(int i = 1; i <= n; ++i)        for(int j = -6000; j <= 6000; ++j)            dp[i][j + 6000] = min(dp[i - 1][j + 6000 + up[i] - down[i]]                                  ,dp[i - 1][j + 6000 - up[i] + down[i]] + 1);//    for(int i = 1; i <= n; ++i)//        for(int j = -6000; j <= 6000; ++j)//            dp[i][j + 6000] = min(dp[i - 1][j + 6000 - up[i] + down[i]]//                                  ,dp[i - 1][j + 6000 + up[i] - down[i]] + 1);//居然也能AC    int ans;    for(int i = 0; i <= 6000; ++i)    {        int ans = min(dp[n][6000 - i],dp[n][6000 + i]);//从0开始,找01-12-2,取最小值        if(ans < 10000)//不是无穷大        {            printf("%d\n",ans);            break;        }    }    return 0;}

解决方法:
最开始想到根据前i-1个骨牌上下点数之差来决定第i个骨牌是否翻转,利用了贪心的思想,但是这样不能保证正确性。
如果将dp[i]设为使前i个骨牌上下点数之差最小所需的最少操作数,这样带有明显的后效性,也不行。似乎陷入了江局…
转换一下思路,与其求最优解问题,不如看一看这个最优解是否存在。即将求最值问题转化为求一个值是否可行的问题。
想到了01背包,翻转相当于放入,不翻转相当于不放入。
翻转操作数就加1,不翻转不加。
于是状态转移方程: dp[i][j] = min{dp[i - 1][j + up[i] - down[i]],dp[i - 1][j - up[i] + down[i]] + 1}
由于会出现负数,所以需要平移