POJ 1014 Dividing 解答

简介: 题目详见http://poj.org/problem?id=1014看到这道题第一反应便知道它是一道类似背包问题的题,  解法我自然而然得从背包问题的解法入手,  网上查了查,  背包问题的基本题型是01背包, 即每种...

题目详见http://poj.org/problem?id=1014

看到这道题第一反应便知道它是一道类似背包问题的题,  解法我自然而然得从背包问题的解法入手,  网上查了查,  背包问题的基本题型是01背包, 即每种物品只有一种, 所以对于每种物品只有两种可能, 取或不取, 而这道题并不符合01背包问题的条件, 题目中每种物品不止一个, 所以情况不仅涉及取或不取, 还要考虑取几个的问题, 这种情况叫做多重背包问题(即每种物品有固定个数, 可能不止一个, 如果每种物品都有无限个, 则叫做完全背包问题). 这种问题一般用动态规划(Dynamic Programming, DP)的算法, 动态规划的话会涉及两方面, 必须满足这两方面, 才能使用动态规划(DP): 一是重叠子问题, 二是最优子结构. 满足重叠子问题, 我们才可以应用该算法避免重叠子问题的重复计算, 这便是DP提高速度的关键所在, 否则也就没有应用的必要; 满足最优子结构, 我们才可以将原问题分解为多个子问题, 并在求取子问题最优解的过程中计算原问题的最优解. 二者缺一不可.

接下来说背包问题的求解, 可以用下面的状态转移方程来表示问题求解的思路:

v[i][j] = max{v[]i-1[j], v[i-1][j-item[i]]+item[i]}

v[i][j]表示在前i件物品选择, 放入容量为j的背包中, 最多可以放的物品价值, item[i]表示第i件物品的价值.

所以, v[i-1][j]表示在前i-1件物品中找不大于j的最优解(即在前i件物品中不选取第i件再选取其他一些后的最优解), 而v[i-1][i-item[i]]表示在前i件物品中选取第i件再选取其他一些后的最优解.

以上是01背包问题用动态规划算法求解时的分析, 而完全背包问题我们可以转换为01背包问题, 我们可以把同一种物品的每一个视作不同种类, 这样原来第i中物品可能有k个, 现在可以理解为有k种不同的物品, 每种一个, 这样每种物品的个数转换为一个, 就变成了01背包问题.

在具体到本题, 可以先排除物品总价值非偶数的情况, 接下来可以将总价值的一般视作背包的大小, 而每个物品的价值视作背包问题中的体积, 本题并不是求最优解, 而是判断最优解是否就是背包本身的大小, 所以可以先求最优解, 再判断.

还有一点就是这道题中限定总物品数最大为20000, 如果只用这种思路可能会超时, 需要对算法加以优化, 减少需求解的情况, 这便用了网上所说的二进制压缩, 具体的证明应该在数论方面的书中, 是一个定理, 大意是说, 任何一个正整数, 均可以有一系列2的指数相加得到, 比如, 21 = 1 + 2 + 2 + 4 + 4 + 8 = 1 + 2*2 + 2*4 + 8 = 2^0 + 2*2^1 + 2*2^2 + 2^3. 依此定理, 每种物品的个数均可以如此表示, 但读者可能会问, 为什么要这样表示? 原因是这样的, 首先我们用这个定理的目的是压缩要求解的子问题的个数, 例如某种物品有21个, 如果单纯按多重背包转01背包的思路, 则新增了20中物品, 而利用二进制压缩, 我们可以将1件该种物品视为替代物品1, 将2件该种物品视为替代物品2, 将4件该种物品视为替代物品3, 依次类推, 这样转成的替代物品较少, 从而实现了压缩, 但有人会担心, 按原来转化的方法, 每件物品取与不取的情况都会考虑, 而二进制压缩后会不会丢解呢, 这种担心是多余的, 举例说明:

一种物品7个, 可用二进制压缩为 1 + 2 + 4,   即1个物品变为替代物品1, 2个物品变为替代物品2, 4个物品变为替代物品3, 如果按原先不压缩的方法, 则变为 1 1 1 1 1 1 1共7个替代物品:

给它们编号则为: (第一排为编号, 第二排为价值)

压缩前                                          压缩后

1   2   3   4   5   6   7                   1     2     3

1   1   1   1   1   1   1                   1     2     4

针对压缩前的每种物品取与不取共2^7=128种情况, 压缩后共2^3=8种情况, 压缩前各种情况可能取到的价值, 压缩后同样能取到, 如取1 ,3, 4, 5则价值为1+1+1+1=4, 则压缩后可用直接用取替代物品(压缩后)3来表示, 取1, 2 3, 4价值为1+1+1+1=4也可以用取替代物品3来表示, 再如取1, 2, 3, 4, 5, 6,7总价值为1+1+1+1+1+1+1=7, 可以表示为1+2+4=7, 即取替代物品1, 2, 3, 所以对于每种压缩前情况, 压缩后均可以表示, 用替代物品的取与不取替代原先较多物品时取与不取的情况, 这便实现了压缩.

具体代码如下(已Accepted): (参考了http://blog.csdn.net/zhu2mu/article/details/6649712)

#include <iostream>

using namespace std;

int v[70000];   // 暂存某一轮指定容量时最大价值 
int item[200];  // 二进制压缩后等价各个物品对应的价值 
const int n=6;  // 原始物品种类数 
int num[n];     // 存储各原始物品数量 

int main(int argc, char * argv[]) 
{   
    int index=0; // 数据集计数 
    while(true){        
        // 初始化, 数组置0 
        memset(v, 0, sizeof(v));
        memset(item, 0, sizeof(item));
        // 输入, 判断输入是否全为0,  
        int sum=0;
        int value=0; 
        for(int i=0;i<n;++i){
            cin>>num[i];
            sum+=num[i];     // 物品总数 
            value+=num[i]*(i+1); // 物品价值和 
        }
        if(0==sum) break;
        ++index;
        cout<<"Collection #"<<index<<":\n"; 
        // 如果总价值为奇数, 则必不可分 
        if(0!=value%2){
            cout<<"Can't be divided.\n"<<endl;
            continue; 
        } 
        
        // 执行二进制压缩过程, 转换成0/1背包 
        int k=0; // 记录二进制压缩后等价物品的数量 
        for(int i=0;i<n;++i){
            int t=1;
            // 针对每一件物品的数量, 转换成对应的二进制数目的等价物品 
            while(num[i]>0){
                if(num[i]>t){
                    item[k++]=t*(i+1);
                    num[i]-=t; 
                    t*=2;
                }else{
                    item[k++]=num[i]*(i+1);
                    num[i]=0;
                }
            }         
        }
        
        
        value/=2;
        // 总共有k件等价物品, 开始0/1背包问题用动态规划自底向上解决之
        // 每一个i代表在前i个物品中计算最优解 
        for(int i=0;i<k;++i){
            // 计算背包不同容量时, 暂时的可装价值
            // 已知前i-1件物品, 在容量分别为j-value时的最优解 
            for(int j=value;j>=item[i];--j){
                if(v[j]<v[j-item[i]]+item[i])
                    // 在计算前i个物品的最优解v[j]时
                    // v[j-item[i]]仍为前i-1个物品的最优解
                    // 相当于计算方程max{v[i-1][j], v[i-1][j-item[i]]+item[i]} 
                    v[j]=v[j-item[i]]+item[i]; 
            } 
        } 
        if(v[value]==value)
            cout<<"Can be divided.\n"<<endl;
        else
            cout<<"Can't be divided\n"<<endl;
    } 
    
    return 0;    
}


目录
相关文章
POJ 2487 Stamps
POJ 2487 Stamps
84 0
poj 2299 求逆序数
http://poj.org/problem?id=2299 #include using namespace std; int aa[500010],bb[500010]; long long s=0; void merge(int l,int m,int r) { ...
767 0
POJ 1804
题目:http://poj.org/problem?id=1804 大意:给你一串数字,排序。求出最少的交换次数  \ 我用归并做的 #include #include using namespace std; int aa[500010],bb[500010]; long lon...
671 0
poj 1455
Description n participants of > sit around the table. Each minute one pair of neighbors can change their places.
599 0
POJ-1003-hangover
Description How far can you make a stack of cards overhang a table? If you have one card, you can create a maximum overhang of half a card length.
737 0
POJ 2487 Stamps
Description Background Everybody hates Raymond. He’s the largest stamp collector on planet earth and because of that he always makes fun of all the others at the stamp collector parties.
1035 0
poj题目分类
http://www.cnblogs.com/kuangbin/archive/2011/07/29/2120667.html
759 0