zoukankan      html  css  js  c++  java
  • BZOJ 2287 【POJ Challenge】消失之物

    2287: 【POJ Challenge】消失之物

    Description

    ftiasch 有 N 个物品, 体积分别是 W1W2, ..., WN。 由于她的疏忽, 第 i 个物品丢失了。 “要使用剩下的 N - 1 物品装满容积为 x 的背包,有几种方法呢?” -- 这是经典的问题了。她把答案记为 Count(i, x) ,想要得到所有1 <= i <= N, 1 <= x <= M的 Count(i, x) 表格。

    Input

    第1行:两个整数 N (1 ≤ N ≤ 2 × 103) 和 M (1 ≤ M ≤ 2 × 103),物品的数量和最大的容积。

    第2行: N 个整数 W1W2, ..., WN, 物品的体积。

    Output

    一个 N × M 的矩阵, Count(i, x)的末位数字。

    Sample Input

    3 2
    1 1 2

    Sample Output

    11
    11
    21

    HINT

    如果物品3丢失的话,只有一种方法装满容量是2的背包,即选择物品1和物品2。


      如果说这是一道水题,不错。但如果说这仅仅是一道水题,那我会很生气。

      背包问题当然很经典。f[i][j]表示前i个物品恰好装满容量为j的背包的方案数。显然可得:

      初始状态:f[0][0]=1,f[0][j]=0(j≠0)

      状态转移:f[i][j]+=f[i-1][j-w[i]](j>w[i])

      然后我们继续。要算出c[i][j]表示除去第i个物品后剩下n-1个物品恰好装满容量为j的背包的方案数。亦可得:

      c[i][0]=1

      c[i][j]=f[n][j] (j<w[i]) 显然i号物品装不下

      c[i][j]=f[n][j]-c[i][j-w[i]](j>=w[i]) f[n][j]是用了n种物品,我们要减去使用了i的方案数。而使用了i的方案数=c[i][j-w[i]]

      当然,我们可以把空间降维,于是空间O(m),时间O(nm)。

      但是!!!

      我们为什么一定要这样做?

      法2:网上有一神犇算出了前缀与后缀的方案数(上述的f即为前缀方案数)。然后对于每一个不能取的i,他对i-1的前缀与i+1的后缀做了值域卷积FFT。空间O(nm),时间O(nmlog m)。

      法3:上面的做法其实十分暴力。但是,我们可以使用一些不一样的做法来实现法2的思想。

      这个方法很值得推广,叫做“分治背包”。

      先想更彻底的暴力。对于每一个物品,我们都需要算出除它以外物品叠加产生的答案。这样的时间复杂度是O(n*n*m)。

      但是,我们可以发现:中间有很多统计是重复的。甚至说,是大多数也不为过。如果可以削去一些重复运算,那么效率会大大提高。

      而现在,我们试图算出1~n这个整体中每一个数的ans,以此减少重复运算,我们可以使用二分。可以用类似于“线段树”的画风来理解这个问题,最开始我们在1~n这条线段上,它在DFS树上有2个儿子,一个是1~mid,另一个是mid+1~rg。这样递归下去,最后lf==rg,我们试图在此时输出i=lf的答案。

      我们已经说过,对于一个数i,我们需要把1~i-1与i+1~n号物品的影响都叠加一遍。而这可以使用DFS回溯解决。现在需要算出lf~rg的答案。可知,mid+1~rg号物品的影响一定会被叠入lf~mid中的每一个物品中。

      话不多说了,直接上代码(多读几遍就懂了):

     1 /**************************************************************
     2     Problem: 2287
     3     User: Doggu
     4     Language: C++
     5     Result: Accepted
     6     Time:368 ms
     7     Memory:924 kb
     8 ****************************************************************/
     9  
    10 #include <cstdio>
    11 const int S = 12;
    12 const int N = 2010;
    13 const int M = 2010;
    14 int n, m, w[N], dp[S][M];
    15 void solve(int dep,int lf,int rg) {
    16     if(lf==rg) {
    17         for( int j = 1; j <= m; j++ ) printf("%d",dp[dep][j]);
    18         printf("
    ");
    19         return ;
    20     }
    21     int mid=(lf+rg)>>1;
    22     for( int j = 0; j <= m; j++ ) dp[dep+1][j]=dp[dep][j];
    23     for( int i = mid+1; i <= rg; i++ )
    24         for( int j = m; j >= w[i]; j-- )
    25             dp[dep+1][j]+=dp[dep+1][j-w[i]], dp[dep+1][j]%=10;
    26     solve(dep+1,lf,mid);
    27     for( int j = 0; j <= m; j++ ) dp[dep+1][j]=dp[dep][j];
    28     for( int i = lf; i <= mid; i++ )
    29         for( int j = m; j >= w[i]; j-- )
    30             dp[dep+1][j]+=dp[dep+1][j-w[i]], dp[dep+1][j]%=10;
    31     solve(dep+1,mid+1,rg);
    32 }
    33 int main() {
    34     scanf("%d%d",&n,&m);
    35     for( int i = 1; i <= n; i++ ) scanf("%d",&w[i]);
    36     dp[0][0]=1;
    37     solve(0,1,n);
    38     return 0;
    39 }
    40 

      如果你看懂了,那我们就可以继续了。因为,背包问题是很容易插入新物品的,且插入的顺序对最后结果不会产生影响。而一般情况下,我们会统计前缀。在分治中,我们的顺序变了,但是效果是完全一样的。请注意,对于“整体DP以去重”这一方法,该条件极其必要的。

      最后说一下,如果用分治背包,空间是O(mlog n)的,时间是O(nmlog n)的。

      如果很懵,或是已经听懂了,可以再看一看:旧题新做:FLOJ Round #19 A 最短路

      附录·速度比较:

    分治背包 924 kb 368 ms 889 B
    法1压缩空间 844 kb 356 ms 546 B
    法1平方空间  16944 kb  364 ms  523 B 
  • 相关阅读:
    struts2+hibernate+Spring分层开发
    (江苏大学)校园网上网帮助工具开发详解(附源码)【行政教学区】【城市热点】
    [CLR的执行模型].NET应用程序是如何执行的?
    ASP.NET服务器对于请求的处理过程
    【简单Web服务器搭建】基于Socket实现的最简单的Web服务器【ASP.NET原理分析】
    ASP.NET 页生命周期和页面事件处理管道
    【分析总结】ASP.NET中的状态管理原理
    【CLRsos调试】关于方法表继承调用问题的总结
    【分析最原始验证码生成】HTTP请求处理程序
    [ASP.NET]Session在多个站点之间共享解决方案
  • 原文地址:https://www.cnblogs.com/Doggu/p/bzoj2287.html
Copyright © 2011-2022 走看看