码迷,mamicode.com
首页 > 其他好文 > 详细

「SDOI2015」序列统计

时间:2020-06-30 22:48:00      阅读:43      评论:0      收藏:0      [点我收藏+]

标签:==   整数   long   ret   时间复杂度   can   scan   efi   cal   

先 dp 一下,设 \(f_{i,j}\)\(i\) 个数字,乘积为 \(j\) 的数列个数

那么显然有 \(f_{i\times 2,j}=\sum_{(k_0 \times k_1) \operatorname{mod} m= j}f_{i,k_0} \times f_{i,k_1}\)

快速幂+暴力卷积,复杂度好像是 \(\mathcal{O}(m^3 \log n)\) ,不能过。

考虑这个乘法卷积咋去做。

加法卷积我们会做(即 \(\operatorname{NTT}\) 板子),那么我们把乘法卷积转为对数搞成加法卷积的形式不就行了吗?

现在问题是如何求模意义下的对数。

\(g\)\(m\) 的原根,那么只需把每个数 \(x\) 转化为 \(\log_g x\) 即可。由于原根的性质保证每个 \(log_g x\) 都有整数解。

(不过 \(x\) 要先膜个 \(m\) 先,这是很显然的吧...

由于 \(g^{\varphi (m)} \equiv 1\),所以这个对数意义下的加法卷积其实要膜的不是 \(m\) 而是 \(\varphi (m)\)...

直接来一发快速幂+加法卷积,时间复杂度是 \(\mathcal{O}(m\log m \log n)\),非常优美,直接过了这题。

代码如下:

#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef long long ll;
const ll mod=1004535809;
int G=3,invG=332748118;
const int N=2400000;
ll ksm(ll b,int n,int md){
	ll res=1;
	while(n){
		if(n&1) res=res*b%md;
		b=b*b%md; n>>=1;
	}
	return res;
}
int tr[N];
void NTT(ll *f,int n,int fl){
	for(int i=0;i<n;++i)
		if(i<tr[i]) swap(f[i],f[tr[i]]);
	for(int p=2;p<=n;p<<=1){
		int len=(p>>1);
		ll w=ksm((fl==0)?G:invG,(mod-1)/p,mod);
		for(int st=0;st<n;st+=p){
			ll buf=1,tmp;
			for(int i=st;i<st+len;++i)
				tmp=buf*f[i+len]%mod,
				f[i+len]=(f[i]-tmp+mod)%mod,
				f[i]=(f[i]+tmp)%mod,
				buf=buf*w%mod;
		}
	}
	if(fl==1){
		ll invN=ksm(n,mod-2,mod);
		for(int i=0;i<n;++i)
			f[i]=(f[i]*invN)%mod;
	}
}
int GetRoot(int x) { 
	int f[10005],tot=0,phi=x-1;
	for(int i=2;i*i<=phi;++i)
		if(phi%i==0){
			while(phi%i==0) phi/=i;
			f[++tot]=i;
		}
	if(phi>1) f[++tot]=phi;
	phi=x-1;
	for(int i=2;i<=phi;++i){
		int fl=1;
		for(int j=1;j<=tot&&fl;++j)
			if(ksm(i,phi/f[j],x)==1) fl=0;
		if(fl) return i;
	}
}
int m;
void Mul(ll *f,ll *g,int n){
	static ll a[N],b[N];
	for(int i=0;i<n;++i)
		a[i]=f[i],b[i]=g[i];
	NTT(a,n,0);NTT(b,n,0);
	for(int i=0;i<n;++i)
		a[i]=1ll*a[i]*b[i]%mod;
	NTT(a,n,1);
	for(int i=0;i<m-1;++i)
		f[i]=(a[i]+a[i+m-1])%mod;
}
ll f[N],g[N];
map<int,int> Log;
signed main(){
	invG=ksm(G,mod-2,mod);
	int n,x,s,X;
	cin>>n>>m>>x>>s;
	int groot=GetRoot(m);
	//cout<<groot<<endl;
	for(int i=0;i<m-1;++i)
		Log[ksm(groot,i,m)]=i;
	for(int i=1;i<=s;++i){
		scanf("%lld",&X);X%=m;
		if(X)f[Log[X]]++;
	}
	X=1;
	g[Log[X]]=1;
	int limit=1;
	while(limit<=(m<<1)) limit<<=1;
	for(int i=0;i<limit;++i)
		tr[i]=(tr[i>>1]>>1)|(i&1?limit>>1:0);
	while(n){
		if(n&1) Mul(g,f,limit);
		Mul(f,f,limit);n>>=1;
	}
	cout<<g[Log[x]];
	return 0;
}

「SDOI2015」序列统计

标签:==   整数   long   ret   时间复杂度   can   scan   efi   cal   

原文地址:https://www.cnblogs.com/limit-ak-ioi/p/13216145.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!