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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值