[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;
}
posted @ 2020-10-19 12:50  BILL666  阅读(98)  评论(0编辑  收藏  举报