首页 > 代码库 > POJ1717 Dominoes DP,背包的变形
POJ1717 Dominoes DP,背包的变形
这道题目比较短,而且有图片很容易懂题意,就是每一张牌,分为上下两部分,上面有几个点,代表上部分为几,下面同样,然后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; }
POJ1717 Dominoes DP,背包的变形