POJ 1742:Coins——单调队列优化的多重背包

转自:http://hi.baidu.com/billdu/item/54bde13de63134be134b1400

题目大意:给你N(1 ≤ N ≤ 100)种钞票以及拥有的张数,以及一个给定的值M(1 ≤ M ≤ 100,000 )。你需要统计出用这些钞票能够凑出来的钱数,在1 - M的范围中有多少个。

楼教主的“男人八题”之一。作为男人八题,那么当然要难一点才有男人的味道,所以说applepi为了成为真男人,在这个题上TLE了10回……不多扯了。多重背包问题,通常采用的方法一般都是二进制拆包,时间复杂度O(VNlogM)。然而,这个题正是BT到这样的时间复杂度过不了的地步。所以说,有必要采用更高级的方式,那就是单调队列。实际上,多重背包的单调队列是我见过的比较麻烦的一种。

众所周知,若用dyna[i][j]表示背包容量为j,物品数为i的最大价值,多重背包的状态转移方程是

dyna[i][j] = max{ dyna[i - 1][j - k * w[i]] + k * c[i] } (0 ≤ k ≤ s[i])

k, c, s分别表示物品的费用,价值和数量。

直接这样上的话,复杂度可是O(VNM)。所以说考虑到方程“向前找”的特性,可以用单调队列来优化状态转移。

首先,单调队列中存放的是前面的价值容量。为什么还要把容量存进去呢?我们把方程变个形看看:

dyna[i][j] = max{ dyna[i - 1][place] + (j - place) / w[i] * c[i] } (place >= j - w[i] * s[i])

dyna[i - 1][place]的值永远都不会变,但是根据place的不同,在dyna[i][j]处所能转移到的值也不一样。所以说必须把容量也存进去才能正确转移。

按照单调队列的特性,每次把新的dyna[i - 1][j - k * w[i]]的值进队,把老的值出队,之后取首个元素就可以了。然而循环的时候顺序要改变,如果要使用单调队列,循环的步长一定是w[i]。那么与首元素对w[i]取模的值不一样的元素就不能更新。要解决这个问题很简单——只需要用两个循环就可以了。模式如下:

for (j = 0; j < w[i]; j++)
{
    for (k = j; k <= m; k += w[i])
    {
        // 状态转移
    }
}

至此这个问题就基本解决了。然而,单纯的O(VN)的多重背包要过这个题还是有点困难,必须进一步优化才可以。这就是各位尚未AC的看官的任务了——男人嘛,一定要自己解决最麻烦的地方。实在是难以解决的,discuss里面有一个解决方案,诸位不妨一试。看一下applepi的代码也可以。

代码如下:

#include
#define max(a,b) ((a)>(b)?(a):(b))

char dyna[100010];
int dqueue[100010], pl[100010], head, tail;
int w[110], s[110], n, m;

int main ()
{
    int i, j, k, l, maxi, ans;
    while (1)
    {
        scanf("%d %d", &n, &m);
        if (n == 0 && m == 0) break;
        for (i = 1; i <= n; i++)
            scanf("%d", &w[i]);
        for (i = 1; i <= n; i++)
            scanf("%d", &s[i]);
        memset(dyna, 0, sizeof(dyna));
        dyna[0] = 1;
        for (i = 1; i <= n; i++)
        {
            maxi = w[i] * s[i];
            if (s[i] == 1)
            {
                for (j = m; j >= w[i]; j--)
                    dyna[j] = max(dyna[j], dyna[j - w[i]]);
            }
            else if (maxi < m)
            {
                for (j = 0; j < w[i]; j++)
                {
                    head = tail = 0;
                    for (k = j; k <= m; k += w[i])
                    {
                        if (tail != head) { if (k - pl[head] > maxi) head++; }
                        if (dyna[k])
                        {
                            dqueue[tail] = dyna[k];
                            pl[tail] = k; tail++;
                        }
                        else if (tail != head) dyna[k] = 1;
                    }
                }
            }
            else
            {
                for (j = w[i]; j <= m; j++)
                    dyna[j] = max(dyna[j], dyna[j - w[i]]);
            }
        }
        ans = 0;
        for (i = 1; i <= m; i++)
            if (dyna[i]) ans++;
        printf("%d\n", ans);
    }
    return 0;
}


你可能感兴趣的:(ACM,动态规划,背包问题)