zoukankan      html  css  js  c++  java
  • 51Nod1957 有限背包计数问题

    传送门

    另一个传送门

    这题还挺有意思……

    先贴一波出题人的题解……

    (啥你说你看不见?看来你还没过啊,等着A了再看或者乖乖花点头盾好了……)

    然后是我的做法……思想都是一样的,只是细节不一样而已……

    令$B=lceil sqrt{n} ceil$,把物品分$ge B$和$<B$两类考虑:

    对于大小$<B$的物品,直接用多重背包计数的方法去做即可,令$f[i][j]$表示使用前$i$个物品凑出$j$的方案数,显然有

    egin{align}f[i][j]=sum_{k=0}^i f[i-1][j-ki]end{align}

    直接算的话时间肯定会爆炸,所以简单变形一下,得到

    $f[i][j]=f[i][j-i]+f[i-1][j]-f[i-1][i(i+1)]$

    应该不难理解,就是对上一个状态的转移区间做一下微调,加上右端点并减掉左端点。

    这样每个状态的计算时间就变成$O(1)$了,而总共有$O(nsqrt{n})$个状态,因此这部分的复杂度就是$O(nsqrt{n})$。

    对于大小$ge B$的物品,显然这些物品是用不完的,直接当作完全背包处理即可。又因为这些物品最多使用$lfloor frac n B floor$个,因此可以令$g[i][j]$表示强制只能使用大小$ge B$的物品时用$i$个物品凑出$j$的方案数。

    直接算不太好算,我们考虑给物品动态添加大小:

    $g[i][j]=g[i-1][j-B]+g[i][j-i]$

    前一种是添加一个大小为$B$的物品,后一种是把所有物品的大小都$+1$,应该不难看出来这样可以做到不重不漏地统计所有方案。

    使用的物品数不会超过$sqrt{n}$,因此状态数仍然是$O(nsqrt{n})$,单次转移$O(1)$,这样后半部分的复杂度就也是$O(nsqrt{n})$了。

    最后合并两部分的结果即可,显然有

    egin{align}Ans=sum_{i=0}^n f[n-i]sum_{j=0}^{lfloorfrac n B floor}g[j][n-i]end{align}

    直接算是$O(nsqrt{n})$的,如果在算$g$的时候顺便算一下$h[j]=sum_i g[i][j]$的话可以降到$O(n)$,再加上分块DP的复杂度,总复杂度就是$O(nsqrt{n})$。

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<cmath>
     4 #include<algorithm>
     5 using namespace std;
     6 const int maxn=100010,p=23333333;
     7 int n,B,f[2][maxn]={{0}},g[2][maxn]={{0}},h[maxn]={0},cur=0,ans=0;
     8 int main(){
     9     scanf("%d",&n);
    10     B=(int)ceil(sqrt(n));
    11     f[0][0]=g[0][0]=h[0]=1;
    12     for(int i=1;i*B<=n;i++){
    13         cur^=1;
    14         for(int j=(i-1)*B;j<i*B;j++)g[cur][j]=0;
    15         for(int j=i*B;j<=n;j++){
    16             g[cur][j]=g[cur^1][j-B];
    17             if(j>=i)g[cur][j]=(g[cur][j]+g[cur][j-i])%p;
    18             h[j]=(h[j]+g[cur][j])%p;
    19         }
    20     }
    21     cur=0;
    22     for(int i=1;i<B;i++){
    23         cur^=1;
    24         for(int j=0;j<=n;j++){
    25             f[cur][j]=f[cur^1][j];
    26             if(j>=i){
    27                 f[cur][j]=(f[cur][j]+f[cur][j-i])%p;
    28                 if(j>=i*(i+1))f[cur][j]=(f[cur][j]-f[cur^1][j-i*(i+1)])%p;
    29             }
    30         }
    31     }
    32     for(int i=0;i<=n;i++)ans=(ans+(int)((long long)f[cur][i]*h[n-i]%p))%p;
    33     if(ans<0)ans+=p;
    34     printf("%d",ans);
    35     return 0;
    36 }
    View Code

    话说听说这题有多项式乘法加速的$O(nlog n)$做法,然而翻了翻51Nod的排行榜并没有看到写这种做法的……看了半天OEIS也没看懂怎么用多项式乘法加速,算了反正分块跑得也挺快我就用分块算了……

  • 相关阅读:
    hdu 4614 线段树 二分
    cf 1066d 思维 二分
    lca 最大生成树 逆向思维 2018 徐州赛区网络预赛j
    rmq学习
    hdu 5692 dfs序 线段树
    dfs序介绍
    poj 3321 dfs序 树状数组 前向星
    cf 1060d 思维贪心
    【PAT甲级】1126 Eulerian Path (25分)
    【PAT甲级】1125 Chain the Ropes (25分)
  • 原文地址:https://www.cnblogs.com/hzoier/p/6412495.html
Copyright © 2011-2022 走看看