DP经典问题:多米诺骨牌(TYVJ 2199, COGS 1205)

来源:互联网 发布:软件改变生活 编辑:程序博客网 时间:2024/06/08 09:06

这道题算是一个经典的DP问题,它可以用背包来写,因为我们知道数据范围是1000,而且,多米诺骨牌的点数是1到6,所以上下之差最多是5000,所以写个背包复杂度上限是O(1k*5k)。
状态f[i][j]表示前i个数,总和达到j时翻的最小牌数。决策同背包,只有两种:翻与不翻,所以方程就是f[i][j] = min(f[i-1][j-a[i]],  f[i-1][j+a[i]] + 1), a[i]表示的是第i个骨牌上下之差(在这里,骨牌的点数没有什么用,所以我们保存的是上下之差。。或下上只差。)。
需要注意的是,C++并没有负下标。所以j需要稍微转化一下,原本j的范围是-5k ~ 5k,把它变成0~1w即可,不过要注意的是,只需要在赋初值f[1][a[1]]与f[1][-a[1]]需要分别在其后加上5*n (注意不是5k,5k是最大数据范围,即n=1k时才加5k)。
求出f数组后就可以找答案了,注意在f数组在赋初值之前要memset一下,设为无穷大。找答案的过程从f[n][j]中找,令j=5*n,因为5*n的实际意义是和为0,之后寻找j+i与j-i,i = 0~5*n,直到找到min(f[n][j+i], f[n][j-i])不是正无穷,这就表示可以达到上下和之差最小为j+i或j-i,输出最小值即可。

#include <cstdio>#include <cstring>#include <algorithm>using namespace std;int n, sum, a[1005], f[1005][10005];int abs(int x) {return x > 0 ? x : -x;}int main(){    memset(f, 0x7f, sizeof f);    scanf("%d", &n);    for(int i = 1; i <= n; i++){        int t1, t2; scanf("%d %d", &t1, &t2);        a[i] = t1 - t2;    }       f[1][a[1]+5*n] = 0;    f[1][-a[1]+5*n] = 1;    for(int i = 2; i <= n; i++)        for(int j = abs(a[i]); j <= 10*n; j++)            f[i][j] = min(f[i-1][j-a[i]], f[i-1][j+a[i]]+1);    for(int i = 0; i <= 5*n; i++){        int t = min(f[n][5*n+i], f[n][5*n-i]);        if(t < 1005) {printf("%d", t); break;}    }   }
0 0
原创粉丝点击