【[SDOI2017]序列计数】

感觉自己的复杂度感人

大概是\(O(p*\pi(m)+p^3logn)\)

还是能过去的

我们看到这么大的数据范围还是应该先想一想暴力怎么写

显然我们可以直接暴力\(dp\)

\(dp[i][j]\)表示已经选择了\(i\)数,其中所有数的和\(mod\ p\)\(j\)的方案数

显然方程是

\[f[i][j]=\sum_{k=1}^mdp[i-1][((j-k)\%p+p)\%p] \]

初始的状态是\(dp[0][0]=1\),最终的答案是\(dp[n][0]\)

至于还有一个至少有一个素数的限制条件,我们可以先不管这个条件直接算一遍,之后再保证\(k\)不为素数再算一遍,两个一减就是答案了

这样暴力转移的复杂度是\(O(nmp)\)的,于是我们要考虑优化

这个转移相当的固定,于是可以矩乘优化

我们发现因为\(p\)非常的小,于是那个膜\(p\)意义下的转移会有很多重复的位置被转移过去,于是我们如果可以预处理出这样一个数组\(tot[j][k]\)

表示\(dp[i-1][k]\)会向\(dp[i][k]\)专一多少次,也就是\(dp[i][k]+=dp[i-1][j]*tot[j][k]\)

于是就有这样一个矩阵会被构造出来

tu

(\(p=3\)的情况)

于是就可以转移了,至于\(tot[j][k]\)怎么求,这个就是很简单了

在没有素数的情况下把所有素数对应的转移减一遍就好了

代码

#include<iostream>
#include<cstring>
#include<bitset>
#include<cstdio>
#define re register
#define maxn 20000005
#define LL long long
const LL mod=20170408;
std::bitset<maxn> f;
int prime[1500000];
LL ans[101][101],a[101][101];
int m,p;
LL n;
inline void did_a()
{
	LL mid[101][101];
	for(re int i=1;i<=p;i++)
		for(re int j=1;j<=p;j++)
			mid[i][j]=a[i][j],a[i][j]=0;
	for(re int i=1;i<=p;i++)
		for(re int j=1;j<=p;j++)
			for(re int k=1;k<=p;k++)
				a[i][j]=(a[i][j]+(mid[i][k]*mid[k][j])%mod)%mod;
}
inline void did_ans()
{
	LL mid[101][101];
	for(re int i=1;i<=p;i++)
		for(re int j=1;j<=p;j++)
			mid[i][j]=ans[i][j],ans[i][j]=0;
	for(re int i=1;i<=p;i++)
		for(re int j=1;j<=p;j++)	
			for(re int k=1;k<=p;k++)
				ans[i][j]=(ans[i][j]+(mid[i][k]*a[k][j])%mod)%mod;
}
inline void Rebuild()
{
	memset(ans,0,sizeof(ans));
	memset(a,0,sizeof(a));
	for(re int i=1;i<=p;i++)
		ans[i][i]=1;
	int t=m/p;
	for(re int i=1;i<=p;i++)
		for(re int j=1;j<=p;j++)
			a[i][j]=t;
	int tot=m%p;
	for(re int i=1;i<=p;i++)
	{
		int cnt=tot,x=i-1;
		if(!x) x=p;
		while(cnt)
		{
			a[i][x]++,cnt--;
			x--;
			if(!x) x=p;
		}
	}
}
inline void out()
{
	for(re int i=1;i<=p;i++)
		{
			for(re int j=1;j<=p;j++)
			printf("%d ",a[i][j]);
			putchar(10);
		}
}
inline void quick(LL b)
{
	while(b)
	{
		if(b&1ll) did_ans();
		b>>=1ll;
		did_a();
	}
}
int main()
{
	scanf("%lld%d%d",&n,&m,&p);
	f[1]=1;
	for(re int i=2;i<=m;i++)
	{
		if(!f[i]) prime[++prime[0]]=i;
		for(re int j=1;j<=prime[0]&&prime[j]*i<=m;j++)
		{
			f[prime[j]*i]=i;
			if(i%prime[j]==0) break;
		}
	}
	Rebuild();
	quick(n);
	LL num=ans[1][1];
	Rebuild();
	for(re int i=1;i<=p;i++)
	{
		for(re int j=1;j<=prime[0];j++)
		{
			a[i][((i-1-prime[j])%p+p)%p+1]--;
			if(a[i][(i-prime[j]+p)%p+1]<0) a[i][(i-prime[j]+p)%p+1]=mod-1;
		}
	}
	quick(n);
	std::cout<<(num-ans[1][1]+mod)%mod;
	return 0;
}
posted @ 2019-01-01 19:59  asuldb  阅读(177)  评论(0编辑  收藏  举报