zoukankan      html  css  js  c++  java
  • hihoCoder #1044 : 状态压缩·一 (清垃圾)

    题意:

      某车厢有一列座位,共有n个位置,清洁工要在这n个位置上清垃圾,但是不能全部位置都清理,只能选择部分。选择的规则是,连续的m个位置内,不能够清理超过q个,也就是说从第1~m个位置最多可以清q个,清q+1个就不行了。当然,q一般是小于m的,不然岂不是可以m个位置全清了?那就没限制了。

    思路:

      看小hi小ho讲的完全是一知半解,看了大神的代码一整天才看懂意思。下面做分析(以我能理解的角度,以及通俗的角度,方便回忆。):

    三个变量的定义:

      n: 一共有n个座位。

      m: 在m个连续的座位。

      q:最多可以清扫的位置个数。

      w[1~n]: 分别对应n个位置上的垃圾数量。(ps:垃圾肯定有多有少,才需要进行决策计算,不然只需要保证m个位置内有q个就行了,q个位置可以随便选)

    问题的分析:

    (1)这不是背包!相同点:都是n个东西,每次多考虑一个。不同点:背包有容量限制可以作为列数进行dp,而这里没有(也就不能开一个二维数组了),却给出了位置的数量方面的限制。

    (2)既然要用动态规划,那就要从小问题开始考虑,而较大问题要依靠较小的问题来进行决策计算。小问题:最小就是只有1个位置了。每次考虑的增加规模量:每次多考虑1个位置,直到n个全考虑了。

    (3)假如m=n,那简单了,问题转成:在n个位置上选出q个,使得垃圾的总数量最多。穷举:2^n种可能,这里边还要去掉一些位置个数大于q的(大于q就是非法),剩下来的才是所有的可能(只是垃圾量的问题,都是合法的)。

    (4)假如n>m,那麻烦了。我们先在前m个进行决策(也就是第3所分析的一样),那么第m+1个应该考虑2~m这里面有没有达到q,达到了,那就只能不清理了;若没有达到,那还可以选择清or不清。为什么会有选择不清的情况?有得清还不赶紧清了吗?原因是,如果你这一步选择清理了,那第m+2个位置就要考虑从3到m+1的位置上有没有达到q,达到了,这一步又肯定没得选择了。假设只有m+2个,前面1~m个位置上已经决策完成,而第m+1个位置上的垃圾极少,第m+2个位置上的垃圾超多,由于在前3~m个位置上的决策只是选了q-1个,但是你在第m+1个位置上选择了清理,那么第m+2个位置上的超多垃圾清不到了。

    (5)结论是:每一次决定第i个位置清or不清理,靠的是前面m-1个位置上的清理数量,跟超过m-1个的都没有任何关系。前面穷举的结果在符合规则的条件下不影响后面的决策,你仍可选择不清理。

    关于实现:

    (1)肯定是个二维数组来保存状态,数组中保存什么?肯定是第i行(包括)之前所做出的决定所清理的垃圾总量。那列是什么?是第i行(包括)之前m个位置上是否清理的信息。考虑到m<=10,那么十位的二进制最多能表达1023,这就是数组列的上限。那么dp[i][j]表示:前 i 个位置在 j (1的个数信息)这种决策情况下所能获得的最大垃圾量。

    (2)看个例子:n有{1, 2, 3, 4, 5, 6, 7, 8, 9}共9个位置,m限制为5,上限q为3。

      {1, 2, 3, 4, 5, 6, 7, 8, 9}  //假设1~5已经考虑完了。准备考虑第6个

      {1, 2, 3, 4, 5, 6, 7, 8, 9}  //考虑6不需要关心1了。而d[6][j]记录的是1~6的的所决定清理的垃圾量,而j只用5个位来表示2~6的存放情况。假设j={10101}b二进制.也就是第2,4,6位置上决定了清理,那么dp[6][j]=" w[] " + w[2]+w[4]+w[6]。这红色的w[]代表第1个的决策结果,具体清没清理,得从dp[5][j>>1]和dp[5][(j>>1)+(1<<5)]看谁大来决定了,小的那个没有必要保存了,1~5的垃圾量明显会比别人小,先淘汰了。

    (3)看代码吧~注释都够能理解的了。

     1 #include <iostream>
     2 #include <cstring>
     3 #include <cstdio>
     4 using namespace std;
     5 const int N=1025;
     6 int w[N];           //权值
     7 int dp[2][N];       //只需要两行就够了,每次计算都要切换到另一行。
     8 int cn[N]={0, 1, 1};          //提前计算好对于任意一个j里面所含的1的个数。
     9 int n, m, q;
    10 const int b[11]={1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024};  //提前计算好M个二进制所能表示的数字的上限。
    11 /*
    12 int cnt(int j)  //还可以用这种方法来统计j的二进制中含1的个数。慢了一些。
    13 {
    14     int maxn=0;
    15     while(j)
    16     {
    17         if(j&1)
    18             maxn++;
    19         j>>=1;
    20     }
    21     return maxn;
    22 }
    23 */
    24 int main()
    25 {
    26     //freopen("input.txt", "r", stdin);
    27     //提前计算好2^m种可能,所产生的1的个数,就不用每次都要数了。
    28     for (int i=3; i<b[10]; ++i)
    29         cn[i] =cn[i>>1] +cn[i&1];         //这里还用到了分治法,每个值的计算还依靠于比其更小规模的结果。
    30 
    31     while(cin>>n>>m>>q)
    32     {
    33         memset(dp, 0, sizeof(dp));
    34         for(int i=1; i<=n; i++)
    35             cin>>w[i];
    36         //核心在这,计算所有可能产生的序列,所产生的最大垃圾量。
    37         int tmp = 1<<m;     //上限
    38         for(int i=1; i<=n; i++)
    39             for(int j=0; j<tmp; j++)
    40                 if( cn[j]<=q )      //前m个位置选择清理的位置数量小于等于q。
    41                     dp[i&1][j]=max( dp[~i&1][j>>1], dp[~i&1][(j>>1)+b[m-1]] ) + (j&1)*w[i]; //i的决策由j的最后一位表示。
    42         //最大垃圾量可能存在于任何排列中,所以要全扫一遍才知道。
    43         int ans=0;
    44         for(int i=1; i<tmp; i++)
    45             ans=max(ans, dp[n&1][i]);
    46         cout<<ans<<endl;
    47     }
    48     return 0;
    49 }
    AC代码
  • 相关阅读:
    行转列
    multipath 安装配置
    网卡绑定
    numa对MySQL多实例性能影响
    Fatal NI connect error 12170
    REVOKE DBA权限要小心
    Oracle 数据库整理表碎片
    listagg 函数
    10046 事件补充
    tkprof 解释
  • 原文地址:https://www.cnblogs.com/xcw0754/p/4453251.html
Copyright © 2011-2022 走看看