hdu1171 (单调队列优化多重背包)

Problem Description
Nowadays, we all know that Computer College is the biggest department in HDU. But, maybe you don’t know that Computer College had ever been split into Computer College and Software College in 2002.
The splitting is absolutely a big event in HDU! At the same time, it is a trouble thing too. All facilities must go halves. First, all facilities are assessed, and two facilities are thought to be same if they have the same value. It is assumed that there is N (0

题意:

给出每个物体的价值和物体的数量,如何分使得A,B所得价值最接近并且A的价值不能小于B

tip:

分两份,尽可能相等,每个最大就是sum/2,把v改成这个就是多重背包的裸题啦

单调队列优化的多重背包模板?:

首先每个物品添加就是d,d+v[I],d+2*v[I]…,d+num*v[I](不超过V)
(d从0-v[I]-1)而且每个只能从相同的d的前面状态转移而来,那么我们就可以外层枚举d了,里面是加多少个v的循环,而假如是p个v,就相当于需要p个,如果一共就k个i物品,那么转移而来的点不能是k-p之前的,因为这样就会加多了,所以记录dp的队列要出队头,一共记录不多于k个,而每次我们要找的就是这k个中最大值,再拿一个单调队列记录一下,从大到小递减,如果dp队列出的队头是当前的最大值,就要在这个队列也出队头,其他的情况就是这个单调队列的头来更新当前dp。。而这个放在dp队列里的和我们正常的dp[j](放j体积的最大价值)不一样,因为还是要保证一点,队列里记录的是整个i物品加之前的值,因为更新的时候不能重复,那怎么做到呢,就是先用这个值-当前放多少个I的体积 这样放到队列里,更新下一个的时候直接+当时多少个I的体积就好了,而因为每个都是+i*num所以不影响单调性的。。

void get_mul(int i){

    for(int d = 0 ; d < v[i] ; d++){

        int stq = 0,edq = -1,stm = 0,edm = -1;

        for(int k = d,cnt =0 ; k <= V; k += v[i],cnt++){

            if(edq == stq+num[i]){

                stq++;

                if(getmax[stm] == q[stq-1])

                    stm++;

            }

            int tmp = dp[k]-cnt*val[i];

            //更新其他k时不出错,相当于到时候强行回归不用i物品的时候dp值

           // cout <<"tmp = "<q[++edq] = tmp;

            while(stm <= edm && q[edm] < tmp)

                edm--;

            getmax[++edm] = tmp;

            dp[k] = getmax[stm]+cnt*val[i];

        }

    }

}

单调队列优化的整体代码:

#include 
#include 
#include 
using namespace std;
int n,sum,V;
const int maxn = 250010;
const int maxm = 5505;
int val[maxn],num[maxn],v[maxn],dp[maxn];
void init(){
    sum = 0;
    memset(dp,0,sizeof(dp));
    for(int i = 1 ;i <= n ;i++){
        scanf("%d%d",&val[i],&num[i]);
        v[i] = val[i];
        sum += val[i]*num[i];
    }
    V = sum/2;
}

void get_01(int i){
    for(int j = V ; j >= v[i];j--){
        dp[j] = max(dp[j],dp[j-v[i]]+val[i]);
    }
}

void get_cp(int i){
    for(int j = v[i] ; j <= V ;j++){
        dp[j] = max(dp[j],dp[j-v[i]]+val[i]);
    }
}
int q[maxn],getmax[maxn];

void get_mul(int i){
    for(int d = 0 ; d < v[i] ; d++){
        int stq = 0,edq = -1,stm = 0,edm = -1;
        for(int k = d,cnt =0 ; k <= V; k += v[i],cnt++){
            if(edq == stq+num[i]){
                stq++;
                if(getmax[stm] == q[stq-1])
                    stm++;
            }
            int tmp = dp[k]-cnt*val[i];
            //更新其他k时不出错,相当于到时候强行回归不用i物品的时候dp值
           // cout <<"tmp = "<q[++edq] = tmp;
            while(stm <= edm && q[edm] < tmp)
                edm--;
            getmax[++edm] = tmp;
            dp[k] = getmax[stm]+cnt*val[i];
        }
    }
}

void sov(){
    for(int i = 1; i <= n ; i++){
        if(num[i] ==1)   get_01(i);
        else if(num[i] * val[i] >= V)   get_cp(i);
        else   get_mul(i);
    }
    int i;
    for(i = V ; i >= 1; i--){
        if(dp[i])   break;
    }
    printf("%d %d\n",sum-dp[i],dp[i]);
}

int main(){
    while(~scanf("%d",&n)&&n > 0){
        init();
        sov();
    }
}

二进制log做法:
原理:用2的次方凑出所有可能性,每个数组写成二进制之后就是2的k次方取或者不取,变成01背包,最后剩下的再取一下就好了==

假设有1000个苹果,可以在每个箱子中放 2^i (i<=0<=n)个苹果,也就是 1、2、4、8、16、32、64、128、256、489(最后的余数)

#include 
#include 
#include 
using namespace std;
const int maxn = 250010;
bool dp[maxn];
int V;
int n;
void zeroOnePack(int cost ,int value){
    for(int i = V; i >= cost ;i--)
        if(dp[i - cost] == true)
            dp[i] = true;
}
void completePack(int cost,int value){
    for(int i = cost ;i <= V ;i++)
        if(dp[i - cost] == true)
            dp[i] = true;
}
void multipPack(int cost ,int value ,int amount){
    if(cost * amount >= V)
        completePack(cost,value);
    else{
        int k = 1;
        while(k < amount ){
            zeroOnePack(cost * k ,value * k);
            amount -= k;
            k *= 2;
        }
        zeroOnePack(cost * amount ,value * amount );
    }
}
int v[510],num[510];
int main(){
    while(~scanf("%d",&n)&&n > 0){
        memset(dp,false,sizeof(dp));
        dp[0] = true;
        V = 0;
        for(int i = 1;i <= n ;i++){
            scanf("%d%d",&v[i],&num[i]);
            V += (v[i] * num[i]);
        }
        int tmp = V;
        V /= 2;
        for(int i = 1;i <= n ;i++){
            multipPack(v[i],1,num[i]);
        }
        int cur=0;
        for(int i = tmp/2;i >= 1 ;i--)
            if(dp[i] == true){
                cur = i;
                break;
            }
        printf("%d %d\n",tmp-cur,cur);
    }
    return 0;
}

你可能感兴趣的:(acm,基本算法)