zoukankan      html  css  js  c++  java
  • NOIP2012普及组T3 摆花

    【题目大意】
    用 n 种花摆共 m 盆花,每盆仅能摆一种花,不分每种花、每盆花的顺序,第 i 种花可以不摆,最多摆 (a_i) 盆,求方案数。
    【输入格式】
    共 2 行。第一行包含两个正整数 n 和 m,中间用一个空格隔开。第二行有 n 个整数,每两个整数之间用一个空格隔开,依次表示 (a_1)(a_2)、……(a_n)
    【输出格式】
    一个整数,方案数对1000007取模的结果。
    【数据范围】
    对于10%的数据, (0<n, m≤50,0≤a_i≤50)
    对于30%的数据, (0<n, m≤100,0≤a_i≤100)
    对于60%的数据,(0<n, m≤2333,0≤a_i≤2333)
    对于100%的数据,(0<n, m≤8848,0≤a_i≤8848)

    〇、网上说法的一些问题

    加了一些特别大的数据是因为网上大部分做法都是(O(nm^2)),没法对付n, m上千的情况。本题有时间(O(nm)),空间(O(n+2m))的做法。
    网上说(O(nm^2))是背包的,是因为

    对于一个给定了背包容量、物品费用、物品间相互关系(分组、依赖等)的背包问题,除了再给定每个物品的价值后求可得到的最大价值外,还可以得到装满背包或将背包装至某一指定容量的方案总数
    对于这类改变问法的问题,一般只需将状态转移方程中的 max 改成 sum 即可。——《背包九讲》

    不过,严格来说这不能算是背包,叫递推可能好点儿,因为动态规划专指解决最优化问题(求最大小快慢)。当然分析时以及递推式与背包很像。

    一、10%,时间(O(n^2m^2)),空间(O(nm))

    最初想的时候把状态搞复杂了……用(f_{i,j})表示用前 i 种花摆前 j 盆时用第 i 种花摆第 j 盆,则可得递推式

    [f_{i,j}=sum_{k=0}^{i-1}sum_{l=j-a_i}^{j-1}f_{k,l} ]

    也就是用前0~i-1种花摆j-(a_i)盆时用第i-1种花摆第j-(a_i)盆(则第 i 种花摆(a_i)盆)的方案数,加上用前0~i-1种花摆j-(a_i)+1盆时用第i-1种花摆第j-(a_i)+1盆(第 i 种花摆(a_i)-1盆)的方案数,一直到用前0~i-1种花摆j-1盆时用第i-1种花摆第j-1盆的方案数。
    另外,在上面的递推式中,如果j<(a_i),递推式变为

    [f_{i,j}=sum_{k=0}^{i-1}sum_{l=0}^{j-1}f_{k,l} ]

    边界条件,(f_{0,0})=1,结果是(f_{i,j}=sum_{i=1}^nf_{i,m})

      f[0][0] = 1;
      for (i = 1; i <= n; i++)
        for (j = 1; j <= m; j++)
          for (k = 0; k < i; k++)
            for (l = max(0, j - a[i]); l < j; l++)
              (f[i][j] += f[k][l]) %= mod;
      for (i = 1; i <= n; i++)
        (ans += f[i][m]) %= mod;
    

    虽说这方法很low但是我最早是通过这个方法推出O(nm)的方法的嗯。

    二、30%,时间(O(nm^2)),空间(O(nm))

    如果(f_{i,j})只是表示用前 i 种花摆前 j 盆(不一定要用第 i 种花),递推式便缩减成

    [f_{i,j}=sum_{k=j-a_i}^{j-1}f_{i-1,k} ]

    也就是(当第 i 种花摆(a_i)盆时)用前i-1种花摆j-(a_i)盆的方案数,加上(当第 i 种花摆(a_i)-1盆时)用前i-1种花摆j-(a_i)+1盆的方案数,一直到(当第 i 种花只摆1盆时)用前i-1种花摆j-1盆的方案数。
    边界变为(f_{i,0})=1,结果是(f_{i,j})

    for (i = 0; i <= n; i++)
      f[i][0] = 1;
    for (i = 1; i <= n; i++)
      for (j = 1; j <= m; j++)
        for (k = max(0, j - a[i]); k < j; k++)
          (f[i][j] += f[i - 1][k]) %= mod;
    ans = f[n][m];
    

    三、60%,时间(O(nm)),空间(O(nm))

    推出这种方法的思路,一种是(O(n^2m^2))的方法加二维前缀和。
    将一维的前缀和推广到二维情况,用(sum_{x,y})表示(sum_{i=0}^xsum_{j=0}^yf_{i,j}),如求(f_{1,2})(f_{4,5})的和(蓝色区域)。
    这里写图片描述
    很明显,蓝色部分的和=图中整个区域-橙色区域-绿色区域+屎黄色区域=(sum_{4,5}-sum_{4,1}-sum_{0,5}+sum_{0,1})

    不过这题的前缀和倒简单一点。当j<(a_i)时,$$f_{i,j}=sum_{k=0}{i-1}sum_{l=0}{j-1}f_{k,l}=sum_{i-1,j-1}$$
    而当j≥(a_i)

    [f_{i,j}=sum_{k=0}^{i-1}sum_{l=j-a_i}^{j-1}f_{k,l}$$注意到k是从0开始的,因此只要把 $l$ 这层拆开。 $$f_{i,j}=sum_{k=0}^{i-1}sum_{l=0}^{j-1}f_{k,l}-sum_{k=0}^{i-1}sum_{l=0}^{j-a_i-1}f_{k,l}=sum_{i-1,j-1}-sum_{i-1,j-a_i-1}]

    这里写图片描述
    也就是(f_{i,j})等于整个蓝色区域减去浅蓝色区域(不要在意为什么图片的字体不同)。
    还有一点是更新sum数组。
    这里写图片描述
    如图,(sum_{6,5}=sum_{4,6}+sum{5,5}-sum_{4,5}+f_{6,5})(sum_{6,5})等于(f_{6,5})(f_{6,5})的左方和上方的所有数之和。用前缀和的思想,(f_{6,5})的左方和上方的所有数一部分是(sum_{4,6}),另一部分是(sum{5,5}),而重叠的部分是(sum_{4,5})
    所以$$sum_{i,j}=sum_{i-1,j}+sum_{i,j-1}-sum_{i-1,j-1}+f_{i,j}$$
    当j<(a_i)时,$$sum_{i,j}=sum_{i-1,j}+sum_{i,j-1}-sum_{i-1,j-1}+sum_{i-1,j-1}=sum_{i-1,j}+sum_{i,j-1}$$
    当j≥(a_i)时,$$sum_{i,j}=sum_{i-1,j}+sum_{i,j-1}-sum_{i-1,j-1}+sum_{i-1,j-1}-sum_{i-1,j-a_i-1}=sum_{i-1,j}+sum_{i,j-1}-sum_{i-1,j-a_i-1}$$
    这样就可以吃掉 f 数组了。
    由于是从(O(n^2m^2))的方法推出来,所以(sum_{0,0}=f_{0,0}=1),由于是前缀和,同理要把(sum_{i,0})(sum_{0,i})都清成1。

    sum[0][0] = 1;
      for (i = 1; i <= n; i++)
        sum[i][0] = 1;
      for (i = 1; i <= m; i++)
        sum[0][i] = 1;
      for (i = 1; i <= n; i++)
        for (j = 1; j <= m; j++)
          if (a[i] <= j)
            sum[i][j] = (sum[i - 1][j] + sum[i][j - 1]) % mod;
          else
            sum[i][j] = (sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - a[i] - 1] + mod) % mod;
      ans = (sum[n][m] - sum[n][m - 1] + mod) % mod;
    

    绕了一大圈,我倒发现这个递推式本不用这么复杂的推导。用(sum_{i,j})表示用前 i 种花摆前0~j盆花(不摆花+只摆第1盆花+摆前2盆花+...+摆前j盆花)的方案数。不用第 i 种花摆第 j 盆的方案数为(sum_{i-1,j})。用第 i 种花摆第 j 盆的方案数为(sum_{i, j-1}),但当j≥(a_i)时,还要减去用前 i-1 种花摆前0~j-(a_i)-1盆花的方案数(因为前提是用第 i 种花摆第 j 盆,当用前 i-1 种花摆前 j-(a_i)-1盆花时,就要摆第 i 种花(a_i)+1盆;当用前 i-1 种花摆前 j-(a_i)-2盆花时,就要摆第 i 种花(a_i)+2盆,以此类推)。

    四、100%,时间(O(nm)),空间(O(n+2m))

    呼我为什么要把那种复杂的分析方法写上……
    在这个数据范围下时间复杂度是可以承受的,但空间炸了。可以注意到无论是(sum_{i,j}=sum_{i-1,j}+sum_{i,j-1})还是(sum_{i,j}=sum_{i-1,j}+sum_{i,j-1}-sum_{i-1,j-a_i-1})的 i 那层只有 i 和 i-1两种情况,所以用滚动数组可以解决空间问题。

    sum[0][0] = sum[1][0] = 1;
      for (i = 1; i <= m; i++)
        sum[0][i] = 1;
    bool t;
      for (i = 1, t = 1; i <= n; i++, t = !t)
        for (j = 1; j <= m; j++)
          if (j <= a[i])
            sum[t][j] = (sum[t][j - 1] + sum[!t][j]) % mod;
          else
            sum[t][j] = (sum[t][j - 1] + sum[!t][j] - sum[!t][j - a[i] - 1] + mod) % mod;
    

    写完后我联系到01背包的空间优化方法本以为有一维数组的做法,但思虑再三发现不行。
    这里写图片描述
    在01背包的空间优化中,求灰色格的值需要用到的两个值都在灰色格的左边和上边。
    这里写图片描述
    但是在本题中,根据递推式,上图中无论是从左往右走还是从右往左走都会有后效性。

  • 相关阅读:
    Laravel 请求:判断是否是 Ajax 请求
    Laravel中常用的几种向视图传递变量的方法
    curl实现http与https请求的方法
    PHP header 的几种用法
    mysql数据库“不能插入中文”解决办法
    支付宝证书签名 PHP SDK
    tp5.0在控制器中和在模板中调用配置文件中的常量
    TP5.1 调用common里面自定义的常量
    Call to a member function assign() on null
    Docker部署code-server
  • 原文地址:https://www.cnblogs.com/P6174/p/7272074.html
Copyright © 2011-2022 走看看