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

原文地址:https://www.cnblogs.com/hzoier/p/6412495.html