[NOI Online #1 入门组]跑步

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;
}
原文地址:https://www.cnblogs.com/zmyzmy/p/13839605.html