[NOI Online #1 入门组]跑步
description:
求整数n的划分数模p的值
data range:
\(N\le 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;
}
NO PAIN NO GAIN