这题还挺有意思……
先贴一波出题人的题解……
(啥你说你看不见?看来你还没过啊,等着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 }
话说听说这题有多项式乘法加速的$O(nlog n)$做法,然而翻了翻51Nod的排行榜并没有看到写这种做法的……看了半天OEIS也没看懂怎么用多项式乘法加速,算了反正分块跑得也挺快我就用分块算了……