poj 2754 Similarity of necklaces 2 转换成多重背包,单调队列优化/ 二进制优化

  贴个官方解题报告

Similarity of necklaces 2

这个问题是一个012背包问题。我们知道01背包只要逆向线性检索,无限背包只要正向检索。012背包就是说,每个物品有一个数量上限。这个问题可以用log方法,也存在线性方法,需要维护一个递增/递减序列。

我们先把所有的Table都放成下限,接下来我们可以算出它距离总和为0还需要增加多少。对于1<=i<=M,它可以看成这样一个物品:体积为Multi[i],费用为Pairs[i],数量为Up[i]-Low[i]。然后就得到一个012背包问题了。

复杂度约为:O(M*背包大小)。其中背包大小不超过M*20*25=200*20*25=100000

 

  单调队列优化解法:

    多重背包状态方程:

        

    令  转换下得到:

        

    因为 , 当 j 确定时, 则x的取值范围为,  

    也就是说    ,  也可看作为 

    则可得

            ,其中 j <= k

    那么意味着,对于确定的 j, 求 Xk 时候,我们只需要对  维护一个单调队列即可。

    对于任意的 Xi < Xj, 若 Xb 优于 Xa,则必将满足如下要求, 

    

    这种写法 AC时间是 1500ms,有点慢    

View Code
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<algorithm>
using namespace std;

const int maxn = 210;
const int N = 100000+10;
const int inf = -2139062144;
int dp[2][N];
int P[maxn], M[maxn], Low[maxn], Up[maxn];
int n, m, Q[N], cnt[maxn], val;
int max(int a,int b) {return a>b?a:b;}
int main()
{ 
    while( scanf("%d", &n) != EOF)
    {
        m = 0; val = 0;    
        for(int i = 1; i <= n; i++)
        {    
            scanf("%d%d%d%d",P+i,M+i,Low+i,Up+i);
            m += Low[i]*M[i];
            val += Low[i]*P[i];    
            cnt[i] = Up[i]-Low[i];    
        }    
        memset(dp, 0x80, sizeof(dp));
        dp[0][0] = 0;    
    
        m = -m;
        int cur = 0;
        for(int i = 1; i <= n; i++)
        {
            int nxt = cur^1;
            // 枚举剩余系j    
            for(int j = 0; j < M[i]; j++)
            {
                //单调队列队首指针qh,队尾指针qe, 当前最大长度len    
                int qh = 0, qe = -1, len = cnt[i]*M[i];
                for(int k = j; k <= m; k += M[i] )
                {
                    if( dp[cur][k] != -inf )
                    {
                        while( (qh <= qe) && (dp[cur][ Q[qe] ]+(k-Q[qe])*P[i]/M[i] <= dp[cur][k] ) )
                            qe--;
                        Q[++qe] = k;    
                        while( (qh <= qe) && (k-Q[qh]>len) ) qh++;
                        if(qh <= qe)    dp[nxt][k] = dp[cur][ Q[qh] ] + (k-Q[qh])*P[i]/M[i];
                        else    dp[nxt][k] = -inf;    
                    }
                }
            }
            cur = nxt;    
        }
        printf("%d\n", dp[cur][m]+val );    
    }
    return 0;
}

 

    

    将其转换成 01背包:

      套用 背包九讲 的二进制写法就可以了,一样将Table取下限,转换出一个物品数量。

      模板是处理第i种物品时, 若其 体积*数量 >= 总背包大小, 则当作完全背包处理,

      否则 将其按照 二进制表示的形式,当作一次 01背包来处理。代码400MS左右,比上面写法快

View Code
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
using namespace std;

const int inf = 0x80808080;
const int N = 100010;
int n, V, val;
int P[210], M[210], cnt[210];
int dp[N];

int max(int a,int b)
{ return a>b?a:b; }

void ZeroOnePack( int cost, int weight )
{
    for(int v = V; v >= cost; v-- )    
        dp[v] = max( dp[v], dp[v-cost] + weight );
}
void CompletePack( int cost, int weight )
{
    for(int v = cost; v <= V; v++ )
        dp[v] = max( dp[v], dp[v-cost] + weight );
}
void MultiplePack( int cost, int weight, int num )
{
    if( cost*num >= V )
    {    
        CompletePack( cost, weight );
        return ;
    }
    int k = 1;
    while( k <= num )
    {
        ZeroOnePack( k*cost, k*weight );
        num = num-k;
        k <<= 1;
    }
    ZeroOnePack( num*cost, num*weight );
}
int main()
{
    while( scanf("%d",&n) != EOF)
    {
        V = 0; val = 0;
        int up, low;
        for(int i = 1; i <= n; i++)
        {
            scanf("%d%d%d%d",P+i,M+i,&low,&up);
            V += low*M[i];
            val += low*P[i];
            cnt[i] = up-low;    
        }
        V = -V;
        memset( dp, 0x80, sizeof(dp));
        dp[0] = 0;
        for(int i = 1; i <= n; i++)
            MultiplePack( M[i], P[i], cnt[i] );    
        
        printf("%d\n", dp[V]+val );
    }    
    return 0;
}

 

    

    

posted @ 2013-01-18 17:42  yefeng1627  阅读(334)  评论(0编辑  收藏  举报

Launch CodeCogs Equation Editor