description:
求整数n的划分数模p的值
data range:
(Nle 10^5) (p<2^{30})
solution:
十分巧妙的题目
很显然地这是一道dp的题目
首先设(f_{i,j})表示把整数i分为若干个不超过j的数的方案数
那么就有(f_{i,j}=f_{i-j,j}+f_{i,j-1})
就是说要么拆分出j来,要么把上限降为j-1
可以发现这是一个完全背包的形式,于是可以把j的那一维去掉优化空间为(O(n))
但是时间复杂度仍然为(O(n^2))考虑如何优化
还有另一种dp的方法:
记(g_{i,j})表示将整数i恰好划分为j个不为零的数的方案数
那么就有(g_{i,j}=g_{i-1,j-1}+g_{i-j,j})
就是说,对于当前的位置要么放一个1后再也不放了,要么给现在的所有位置都加上1
虽然这个dp本质上仍然是(O(n^2)),但是结合二者我们就可以有神奇的做法
记(m=sqrt n)
对于(j<m)计算出所有的(f_{i,j})
然后把(g_{i,j})的定义魔改为用j个大于等于m的数和为i的方案数
那么有类似转移:(g_{i,j}=g_{i-1,j-m}+g_{i-j,j})
这样可以求出所有(g_{i,j})
那么最后的答案是什么呢?
可以发现(f,g)是没有关系的,因此答案就是(sum_{i=0}^nf_{i,m-1}*sum_{j=0}^mg_{n-i,j})
time space complexity:
time&space:(O(n^{frac{3}{2}}))
code:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,SQRT=350;
int m,n,p;
int f[N],g[N][SQRT];
inline int add(int x,int y){x+=y;return x>=p?x-p:x;}
int main()
{
scanf("%d%d",&n,&p);
m=sqrt(n)+1;
f[0]=1;
for(int i=1;i<m;++i)
for(int j=i;j<=n;++j)
f[j]=add(f[j],f[j-i]);
g[0][0]=1;
for(int i=1;i<m;++i)
for(int j=i;j<=n;++j)
{
g[j][i]=g[j-i][i];
if(j>=m)g[j][i]=add(g[j][i],g[j-m][i-1]);
}
int ans=0;
for(int i=0;i<=n;++i)
{
int anss=0;
for(int j=0;j<m;++j)anss=add(anss,g[n-i][j]);
ans=add(ans,1ll*anss*f[i]%p);
}
cout<<ans%p;
return 0;
}