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