• 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也没看懂怎么用多项式乘法加速,算了反正分块跑得也挺快我就用分块算了……

  • 相关阅读:
    用WebStorm运行Vue项目
    秋招圆满结束
    最新的秋招进度 10月21号
    来更新一下秋招的进度~
    华为三面完进池子啦~9月17日
    C++ 迭代器失效问题
    C++类相关问题(空类、多态、重载、继承)
    C++各种变量、类型的存储位置
    写一个面试中场景题的总结
    明天要面阿里HR面了
  • 原文地址:https://www.cnblogs.com/hzoier/p/6412495.html
Copyright © 2020-2023  润新知