01背包练习总结

6 篇文章 0 订阅
1 篇文章 0 订阅

01背包问题


这两天在做01背包相关的题,我是从这位高手的博客里面看的题,他已经把类归好了,所以跟着练就行了

神牛牛牛的01背包

这几道题感觉确实还是训练脑壳,虽然上学期刚刚学了01背包问题,但是感觉就是期末最后一道大题用了一哈,而且就是切用那个套路,没啥意思,但是通过这几道题,感觉确实不一样。

第一道题poj3624
嗨呀~~~看了题的第一反应,还是感觉没意思,就是切那个套路嘛,但是就是通不过,每次结果都是Memory Limit Exceeded, 而且我感觉直接申明个3403*12881的int数组没得问题三,内存限制是64M得嘛,扛得住三,每次都说我超内存,简直是鬼火冒,然后我就看神牛牛牛的代码,嗨呀~恍然大悟,老师讲过的嘛,就是节约内存的那种方法,只用申明一个一位数组就搞定了,而且我也晓得了,内存限制申明变量用的所有内存,多半要把所有程序用的内存区包括啥子函数调用、内存分配。。。全部算进切,所以啥子递归用多了,还有后头我用vector都超内存了。。。 好像说的vector如果空间不够用的话会两倍的申请空间,一遇到不够用了就翻一倍。。 相当于我就是用的他的代码就不贴了

第二道题poj3628
跟上道题的不同就是没有总量,或者其实可以说他的价值和重量是一样的,但是多半是经过了上道题,有了心理阴影,不敢随随便便的申请空间了,再加上看到他的最大高度到了1000000,而且杆最多有20个,我当时感觉就算申请个一位数组也遭不住三(其实完全没问题) , 然后我又想啊,那个杆最多才20,才1024 * 1024, 2^20次方就可以比把所有子集都列举出来,之所以要2^20次方就是为了保存上一次的结果,哎呀,就是动态规划的本质嘛,程序设计与挑战里面的也是这么说的三~ 下面是大概过程

假设有A,B,C,D个元素,现在要求他们的所有子集
学过离散数学后,我知道了在有n个元素的集合中,他的所有子集有2^n个
子集就是从这n个元素中分别算出 :只包含一个元素的集合、只包含连个元素的集合、只包含三个元素的集合。。。。
所以申明一个2^20次方的数组,也就是1024 * 1024 这么大
第一步:A
第二步:A B AB
第三步:A B AB C AC BC ABC
第四步:A B AB AC BC ABC D AD BD ABD ACD BCD ABCD



你懂~
然后所有的情况都有了,就从中找出超出最小的集合就可以了,下面是代码,但是别个神牛牛牛。。算了,以后叫他三牛吧。。。还是牛三比较亲切,牛三真的强,不管是代码的效率还是代码可读性我都没办法比

#include<iostream>
using namespace std;
int shuzu[1024 * 1024 + 1];
int input[21];
int n, m;

int main(){
    cin >> n >> m;
    for(int i = 0; i < n; i++) cin >> input[i];
    shuzu[0] = input[0];
    int length = 1;
    int answer = 999999999;
    int temp ;
    for(int cishu = 1; cishu < n; cishu ++){        
        for(int i = 0; i < length; i++){
            shuzu[i+length] = shuzu[i] + input[cishu];          
            temp = shuzu[i+length] - m;
            if(temp>=0 && answer>temp) answer = temp;
        }       
        shuzu[2*length] = input[cishu];
        length = 2*length + 1;<< endl;
    }

    if(answer == 999999999) answer = input[0] - m;
    cout << answer << endl;
    return 0;
}

第三题poj1745
+号和-号就这两种情况,但是一直下去就是2^n,然后最多又有9999和符号,2^9999还得了,那么长,不可能向上一道才20
这道题我承认,我没想出来的时候偷瞟了一眼,说实话真的后悔,感觉真的。。。真的,要是我再多想半个小时,或者一个小时说不定我真的可以想出来,就是通过取模,就可以把范围缩小。这道题说了个最后结果除以k取模,有事加减法的话就可以随时取模(数论小知识,草稿纸上画一画就晓得了),既然取k的模,那最后结果不可能大于k。

第四题poj1976
这道题其实就是相当于给你一个集合,从中选出三个,求和最大的结果,选的时候还有间隔。我是每次从集合中拿出一个,挨个挨个的拿出来,然后记录只有一个元素和只有两个元素的情况,因为如果是三个元素的话就不用记录了,直接拿去和最终的答案比较最后最大的那个就出来了。

假设集合中有:A , B , C , D 这四个元素
取A: A , __
取B: B , AB
取C: C , (取AB,AC中最大的那个放在这里) 然后将ABC答案比较
取D: D , MAX(AD,BD,CD) 将BCD,ACD和答案比较

#include<iostream>
#include<cstring>
using namespace std;
int input[50005];
int shuzu[50005][2];

int main(){     
    int jishu, length, len, each;
    cin >> jishu;
    while(jishu --){
        memset(shuzu, 0, sizeof(shuzu));
        cin >> length;
        for(int i = 0; i < length; i++) cin >> input[i];
        cin >> each;
        len = length - each + 1;
        for(int i = 0; i < each; i++) shuzu[0][0] += input[i];
        for(int i = 1; i < len; i++){
            shuzu[i][0] = shuzu[i-1][0] - input[i-1] + input[i-1+each];
        }

        int temp3 = 0;
        for(int z = 0; z < len; z++){
            int temp2 = 0;          
            for(int i = 0; i <= z-each; i++){
                if(temp2 < shuzu[z][0]+shuzu[i][0])
                   temp2 = shuzu[z][0]+shuzu[i][0];
                if(shuzu[i][1]>0 && temp3 < shuzu[z][0]+shuzu[i][1])
                   temp3 = shuzu[z][0]+shuzu[i][1];
            }
            shuzu[z][1] = temp2;
        }
        cout << temp3 << endl;
    }
    return 0;
}

第五题poj1837
第五题一来就收获了一个知识,balance还有天平的意思,还是可以~, 这道题就是在每个钩子上挂不同的重物,我还是枚举所有的情况,虽然最多有20个重物和20个钩子,所以最多的情况有20^20次方种,肯定是装不下的,但是这自然又想到了前面加减法的那道题,如果他们的范围可以确定而且范围过比较小的情况下,就可以用数组存下来,跟取模的解法是异曲同工,范围最大是(5+6+ 。。。+ 24 + 25)*20 / 2 因为有正负之分,所以建两倍长的数组,然后就开始一个一个的把重物考虑进来,让每一个重物都在每一个钩子上面挂一下,然后用初中学的杠杆的知识(哦~ 在这里一定要注意一下~搞清楚好~一定要搞清楚~),放了以后在求和,在放到数组中,这里有一个关键问题就是一定要记录下次数,因为虽然范围可以确定,他们所有的情况的结果都逃不出这个范围,但是正是因为这样会有重叠,而这道题就是要求有几种情况可以让最后的balance平衡,也就是他们最后的和是0,但是如果直接在对应的数组下标那个位置把 0 置为 1 的话,就得不到想要的结果。所以每次得到结果就在结果对应的下标那加一,当时我还wrong answer 了几次,后头才突然恍然大悟(没有看答案~),我最后用的是结构体来存,结果AC过后突然反应过来,其实就把次数装到数组里面就可以了。。

#include<iostream>
#include<cstring>
using namespace std;
typedef struct Node{
    int value;
    int count;
    Node(){value = count = 0;}
}Node;
int length, len;
Node shuzu[21][15002];
int weight[21][21];
int hook[21];

int main(){
    cin >> length >> len;
    for(int i = 0; i < length; i++) cin >> hook[i];
    for(int i = 0; i < len; i++){
        int temp;
        cin >> temp;
        for(int j = 0; j < length; j++){
            weight[i][j] = temp * hook[j];
        }
    }

    int answer = 0;
    int zhong = 7500;
    for(int i = 0; i < length; i++) {
        shuzu[0][weight[0][i]+zhong].value = shuzu[0][weight[0][i]+zhong].count = 1; 
    }
    for(int i = 1; i < len; i++){
        for(int j = 0; j < 15000; j++){
            if(shuzu[i-1][j].value == 0) continue;
            int index = j - zhong;
            int temp;
            for(int z = 0; z < length; z++){
                temp = index + weight[i][z] + zhong;
                shuzu[i][temp].value = 1;
                shuzu[i][temp].count += shuzu[i-1][j].count;

            }
        }       
    }
    cout << shuzu[len-1][zhong].count << endl;
    return 0;
}

最后poj1948
这道题想的最久,开始范围确定没把问题想透,一直想的是用三条边加起来的最大值来申明数组,那可是1800再乘以40,然后这个数量级的平方肯定超时,后面突然恍悟超出一半得嘛,嗨呀~突然就感觉轻松多了,但是我始终感觉跟前面一样,先把所有子集枚举出来,然后早从中选两个,第三个就知道了,但是还是有一个问题就是求那两条边的时候,我开始想的只有从这40*800 中循环两遍,也就是n^2, 还是超时,我还想了用vector来存下标,面的每次一个一个切找值不为零的元素,结果又超内存了,就是我前面提到的vector的申请内存和访问消耗的内存还是不小,我就放弃用vector了,没法感觉两条边必须来2^n, 但是突然我想到因为这三条边要包含所有的值,那最后一个值一定在一条边中,也就是说,最后那个元素也就是最后一维数组中存的所有的数据一定会出现一条边中,反正边之间没有差别,就直接认为会出现在第一条边中,所以!!!复杂度就顺流直下三千尺。。。变成之前的(800X40) X (800X40) 就变成了 800x(40x800),然后终于。。。AC的那一刻我沸腾了,就好像当年麦迪那最后的35秒。。
哎,说实话,牛三真的强,所有的代码都比我简洁高效。。。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
using namespace std;
#define N 805
int length;
long long shuzu[41][N] = {0};
int input[41];
int sum  = 0;
double answer = -1;

int main(){
    cin >> length;
    for(int i = 0; i < length; i++) {cin >> input[i]; sum += input[i]; }
    shuzu[0][input[0]] = 1; 
    double p = sum*1.0 / 2;
    long long a = 1;
    for(int i = 1; i < length;  i++){
        for(int j = 0; j < i; j++){
            for(int z = 0; z < N; z++){
                if(!shuzu[j][z] || input[i]+z>=N) continue;
                shuzu[i][input[i] + z] = shuzu[j][z] | (a << i);
            }
        }
        shuzu[i][input[i]] = (a << i);              
    }   

    for(int i = 0; i < length-1; i++){
        for(int j = 0; j < N; j++){
            if(!shuzu[i][j]) continue;
            long long temp1 = shuzu[i][j];
            for(int z = 0; z < N; z++){
                if(!shuzu[length-1][z]) continue;
                long long temp2 = shuzu[length-1][z];
                if((temp1&temp2) != 0) continue;
                int d = sum - j - z;                
                if(j+z<=d || j+d<=z || z+d<=j) continue;
                double s = sqrt(p*(p-j)*(p-z)*(p-d));                                                               
                if(answer < s) answer = s;
            }
        }
    }

    if(answer < 0) cout << "-1" << endl;
    else  cout << (long long )(answer*100) << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值