poj 1014

 参照了毛子青的《动态规划算法的优化》

[问题描述]

有价值分别为1..6的大理石各a[1..6]块,现要将它们分成两部分,使得两部分价值和相等,问是否可以实现。其中大理石的总数不超过20000

令S=∑(i*a[i]),若S为奇数,则不可能实现,否则令Mid=S/2,则问题转化为能否从给定的大理石中选取部分大理石,使其价值和为Mid。

这实际上是母函数问题,用动态规划求解也是等价的。

m[i, j],0≤i≤6,0≤j≤Mid,表示能否从价值为1..i的大理石中选出部分大理石,使其价值和为j,若能,则用true表示,否则用false表示。则状态转移方程为:

m[i, j]=m[i, j]  OR  m[i-1,j-i*k]         (0≤k≤a[i])

规划的边界条件为:m[i,0]=true; 0≤i≤6

若m[i, Mid]=true,0≤i≤6

但这样做超时

实现代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
using namespace std;

int const maxn = 30010;

bool dp[7][maxn];
int value[7];

int main()
{
   
    int num = 1;
    while(true)
    {
        memset(dp, 0, sizeof(dp));
        
        int val = 0;
        for(int i = 1; i <= 6; i++)
        {
            scanf("%d", &value[i]);
            val += i * value[i];
        }
        if(val == 0)
            break;
            
        printf("Collection #%d:\n", num++);
        
        if(val % 2 == 1)
        {
            printf("Can't be divided.\n\n");
            continue;
        }
        int mid = val / 2;
        for(int i = 0; i <= 6; i++)
            dp[i][0] = true;
        for(int i = 1; i <= 6; i++)
        {
            for(int j = 0; j <= mid; j++)
            {
                for(int k = 0; k <= value[i]; k++)
                {
                    if(j >= k * i)
                        dp[i][j] |= dp[i-1][j - k * i];
                }
            }
        }
        if(dp[6][mid])
            printf("Can be divided.\n\n");
        else
            printf("Can't be divided.\n\n");
    }
    
    return 0;
}


按照他说的双向动态规划~

状态转移方程改进为:

当i≤3时:

m[i, j]=m[i, j]  OR  m[i-1,j-i*k]         (1≤k≤a[i])

当i>3时:

m[i, j]=m[i, j]  OR  m[i+1,j-i*k]        (1≤k≤a[i])

规划的边界条件为:m[i,0]=true; 0≤i≤7

这样,若存在k,使得m[3,k]=true, m[4,Mid-k]=true

我不知道自己理解错误还是,代码实现错误,还是超时~~(有谁用这种方法实现过了的??)

代码实现

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>

using namespace std;

int const maxn = 30010;
int value[7];
bool dp[7][maxn];

int main()
{
    
    int num = 1;
    while(true)
    {
        memset(dp, 0, sizeof(dp));
        int val = 0;
        for(int i = 1; i <= 6; i++)
        {
            scanf("%d", &value[i]);
            val += i * value[i];
        }
        if(val == 0)
            break;

        printf("Collection #%d:\n", num++);

        if(val % 2 == 1)
        {
            printf("Can't be divided.\n\n");
            continue;
        }
        int mid = val / 2;
        for(int i = 0; i <= 7; i++)
            dp[i][0] = true;
        for(int i1 = 1, i2 = 6; i1 <= 3 || i2 > 3; i1++, i2--)
        {
            for(int j = 0; j <= mid; j++)
            {
                for(int k = 0; k <= value[i1] || k <= value[i2]; k++)
                {
                    if(k <= value[i1])
                    {
                        if(j >= k * i1)
                            dp[i1][j] |= dp[i1-1][j - k * i1];
                    }
                    if(k <= value[i2])
                    {
                        if(j >= k * i2)
                            dp[i2][j] |= dp[i2+1][j - k * i2];
                    }
                }
            }
        }
        bool flag = false;
        for(int k = 0; k <= mid; k++)
        {
            if(dp[3][k] && dp[4][mid-k])
            {
                flag = true;
                printf("Can be divided.\n\n");
                break;
            }
        }
        if(!flag)
            printf("Can't be divided.\n\n");
    }
    
    return 0;
}


上面两种方法都超时,后面看了http://blog.csdn.net/zxy_snow/article/details/6169008这位神牛的,是用二进制拆分实现的,直接抄过来吧。。。。。

这个问题其实就是0/1背包问题,只是维度不是之前的1维了,因为每种分数的大理石不止一个,是多个。

所以我们可以转成一维的,把每种分数多少个大理石从小到大摊开来,比如所1分的大理石有两个,2分的大理石有3个,那么从小到大排列就是1*1, 1*1, 2 *1, 2 * 2, 被乘数表示分数,乘数表示个数,然后就可以用0/1背包来解决了

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>

using namespace std;

int const maxn = 300010;
int value[7], num[maxn], tot;
int dp[maxn];

void split(int n, int v);

int main()
{
   
    int n= 1;
    while(true)
    {
        memset(dp, 0, sizeof(dp));
        int val = 0;
        for(int i = 1; i <= 6; i++)
        {
            scanf("%d", &value[i]);
            val += i * value[i];
        }
        if(val == 0)
            break;

        printf("Collection #%d:\n", n++);

        if(val % 2 == 1)
        {
            printf("Can't be divided.\n\n");
            continue;
        }
        int mid = val / 2;
       
        tot = 0;
        for(int i = 1; i <= 6; i++)
            split(value[i], i);
        for(int i = 0; i < tot; i++)
        {
            for(int j = mid; j >= num[i]; j--)
            {
                if(dp[j - num[i]] + num[i] > dp[j])
                    dp[j] = dp[j - num[i]] + num[i];
            }
        }
        
        if(dp[mid] == mid)
            printf("Can be divided.\n\n");
        else
            printf("Can't be divided.\n\n");
    }
    
    return 0;
}

void split(int n, int v)
{
    int i = 0, x, tmp = 0;
    while(true)
    {
        x = 1 << i;
        if(tmp + x > n)
            break;
        tmp += x;
        num[tot++] = x * v;
        i++;
    }
    x = n - tmp;
    if(x != 0)
        num[tot++] = v * x;
}


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值