POJ1717 Dominoes DP,背包的变形

来源:互联网 发布:git ubuntu安装 编辑:程序博客网 时间:2024/06/16 09:56

这道题目比较短,而且有图片很容易懂题意,就是每一张牌,分为上下两部分,上面有几个点,代表上部分为几,下面同样,然后n张牌平行竖直放置,这样每一张牌的上面部分组成第一行,下面部分组成第二行,上下两行的和是有差异的值为gap,每一张牌可以上下反一下,这样可以是的差异值gap缩小,问你使得gap值最小 需要翻牌的最少次数

首先这道题目跟POJ1837有点类似,但是 边界设置这道题明显会麻烦许多,因为之前做过了POJ1837,所以这道题一上来就选择了那个方法,但是a,b值的差异极端为6000,又有负的,为了代表负数 所以数组就得开到12000,这样n又为1000,就是10的7次方多的这样就会超时,所以一直在想办法优化,去推一维的,然后想到了办法简直,可以设定一个边界,然后每次从左边扫到右边的范围 根据a,b的输入来进行更新,这样就不用每次都那么极端的去扫12000了,于是开始敲起来,
因为有a - b可能产生负数,所以就数组开为最大值的两倍也就是 6000 * 2,然后边界设定在6000,dp[i][j]代表前i个此时差异值为j的 最少翻转次数,状态转移就是背包的转移方式

dp[i][j] = min(dp[i][j],dp[i-1][j - (ai - bi)] + 1);

然后就是因为题目所求的值的只是存在的缘故所以可以优化,这个具体的理由这个博客说的很好:戳这里


但是我一直RE,后来看到这个博主把边界设定在20000,为什么是20000呢,我RE肯定是RE在数组的下边界,因为有个减法的存在可能会导致下标为负数的,一直没想明白,结果就从下午拖到了晚上,后来发现错在这里 ,就是假设gap值为 x ,那么 某一张牌的 上面值a = 2,下面值b = 1,翻转以后 gap值会缩小2,也就是x - 2,这样开6000就不行了,因为极端情况会产生减去12000的情况,最后经过了 20多把的提交终于AC了

而后我又让一个巨巨去做了一下,他直接用了我一开始想到的那个办法,也就是我认为会超时的办法,结果他过了,而且 他第一次交的数组也不够大,也过了,这道题数据应该是有问题的,同时也给出那个巨巨的代码


int nnn;int aa[1000 + 55];int bb[1000 + 55];int cc[1000 + 55];int dp[100000 + 55];int suma,sumb;int sumcc;int le,ri;void init() {suma = sumb = sumcc = 0;memset(aa,0,sizeof(aa));memset(bb,0,sizeof(bb));memset(cc,0,sizeof(cc));memset(dp,0x7f,sizeof(dp));}int Abs(int x) {return x < 0 ? -x : x;}bool input() {while(cin>>nnn) {for(int i=1;i<=nnn;i++) {cin>>aa[i]>>bb[i];suma += aa[i];sumb += bb[i];cc[i] = aa[i] - bb[i];sumcc += cc[i];}return false;}return true;}void cal() {int ans = dp[0];le = ri = 12000;dp[12000] = 0;for(int i=1;i<=nnn;i++) {if(cc[i] > 0)for(int j=ri + cc[i] * 2;j>=le + cc[i] * 2;j--)dp[j] = min(dp[j],dp[j - cc[i] * 2] + 1);else if(cc[i] < 0)for(int j=le + cc[i] * 2;j<=ri + cc[i] * 2;j++)dp[j] = min(dp[j],dp[j - cc[i] * 2] + 1);ri = max(ri,ri + cc[i] * 2);le = min(le,le + cc[i] * 2);}for(int i=0;i<=nnn;i++) {if(12000 + sumcc - i < 0)continue;/******/if(ans > dp[12000 + sumcc - i] || ans > dp[12000 + sumcc + i]) {ans = min(dp[12000 + sumcc - i],dp[12000 + sumcc + i]);cout<<ans<<endl;return ;}}}void output() {}int main() {while(true) {init();if(input())return 0;cal();output();}return 0;}

巨巨的代码:


#include <cstdio>#include <cstring>#include <algorithm>using namespace std;const int N = 1000+5;const int Inf = 1<<30;int dp[N][N<<4], a[N], b[N];int n;inline void Update(int &x, int y) {    if(x > y)   x = y;}void solve() {    int mid = 6000;    for(int i = 0;i <= n; i++) {        for(int j = 0;j <= mid*2; j++)            dp[i][j] = Inf;    }    dp[0][mid] = 0;    for(int i = 1;i <= n; i++) {        for(int j = 0;j <= mid*2; j++) if(dp[i-1][j] < Inf) {            Update(dp[i][j+a[i]-b[i]], dp[i-1][j]);            Update(dp[i][j-a[i]+b[i]], dp[i-1][j]+1);        }    }    for(int i = 0;i <= mid; i++) if(dp[n][i+mid]<Inf || dp[n][mid-i]<Inf){        printf("%d\n", min(dp[n][i+mid], dp[n][mid-i]));        return ;    }}int main() {    scanf("%d", &n);    for(int i = 1;i <= n; i++) {        scanf("%d%d", &a[i], &b[i]);    }    solve();    return 0;}


0 0