[Poj1717]&[洛谷1282]多米诺骨牌 背包Dp

题目链接:Poj1717 , 洛谷1282 .

—————————————-

概述

给你 n 对数,每一对数分为上下两部分,这两个部分各有一个值,你可以交换上下两个数的位置。记上部分的和为 s1 ,下部分的和为 s2 ,现求使得 |s1s2| 达到最小的交换次数。

—————————————-

题解

由题目的叙述我们可以知道,每一对数要么不交换,要交换最多被交换一次,因为再交换一次就会换回来。

咦?每一组数要么不操作,要么只操作一次,和01背包怎么那么像?

那我们试着用01背包的套路来定义状态,看看可不可行。

dp[i] 表示使 s1s2=i 的最小操作数,初始化 dp[s1s2]=0 ,其它为 。为了方便理解, i 可以取到负数。
ai 表示初始时第 i 组数的上半部分, bi 表示下半部分,那么此时这一组数 i 的差为 d=aibi
不妨先令 ai>bi ,假如我们选择不交换,那么差还是 d ,假如交换就变成了 d s1s2 减少了 2×d

所以我们可以得到状态转移方程:

dp[i]=dp[i+2×d]+1.

对于 ai<bi ,交换之后会使 s1s2 增加 2×d ,那么转移方程则是:

dp[i]=dp[i2×d]+1.

最后统计答案的时候就从 dp[0] 开始左右扩展,假如有 dp[i] 不为 ,那么答案就是 dp[i].

差不多就是这样了?好像的确没有后效性。

至于 i 的下标会取到负数,我们可以将数组整体向数轴正半轴平移,这样就能使所有的 i 取到正数了。

—————————————-

代码

#include
#include
#include
#include
#include
#include
#include
#define ll long long
#define For(i,j,k) for(register int i=j; i<=(int)k; ++i)
#define Forr(i,j,k) for(register int i=j; i>=(int)k; --i)
#define INF 0x3f3f3f3f
using namespace std;

const int maxn = 1000+5;
const int num = 5050;

int n, s, Ans;
int a[maxn], dp[(maxn*6)<<1];

inline void read(int &x){
    char c;
    while((c=getchar())<'0' || c>'9');
    x = c^48;
    while((c=getchar())>='0' && c<='9')
        x = x*10+(c^48);
}

inline void chkmin(int &x, int y){
    if(y < x)
        x = y;
}

int main(){
    int x, y, d;

    read(n);
    For(i, 1, n){
        read(x), read(y);
        s += (a[i] = x-y);
    }

    memset(dp, INF, sizeof(dp));
    dp[s+num] = 0;//初始化,加num即让数组整体向右平移.
    For(i, 1, n)
        if(!a[i])    continue;
        else if(a[i] > 0){//ai > bi 的情况.
            d = a[i]<<1;
            For(j, -5000+num, 5000-d+num)//5000为s1-s2的最大值. 
                chkmin(dp[j], dp[j+d]+1);
        }
        else{//ai < bi 的情况.
            d = (-a[i])<<1;
            Forr(j, 5000+num, -5000+d+num)
                chkmin(dp[j], dp[j-d]+1);
        }

    Ans = INF;
    if(dp[num] != INF)    Ans = dp[num];
    else{
        For(i, 1, 5000){
            if(dp[num-i] != INF)    chkmin(Ans, dp[num-i]);
            if(dp[num+i] != INF)    chkmin(Ans, dp[num+i]);
            if(Ans != INF)    break;
        }//从dp[0]开始左右扩展,统计答案. 
    }

    printf("%d", Ans);
    return 0;
}

—————————————-

小结

此题考察的算法其实比较基础,重点在于分析问题原本的性质,即每一组数只有(不交换\交换一次)这两种选择。当分析出这题的本质就是01背包时,解法自然而然就出来了。

—————————————-

wrote by miraclejzd.

你可能感兴趣的:(动态规划)