SDOI2015 序列统计

博客详细介绍了如何使用离散对数和快速幂算法解决一道数学问题,即在给定序列中找到乘积模质数m同余于x的组合数。通过将序列元素和x取离散对数,将原问题转换为求和问题,利用NTT加速卷积进行求解,同时强调了处理序列中元素为0的特殊情况的重要性。
摘要由CSDN通过智能技术生成

题目链接:https://www.luogu.com.cn/problem/P3321
题目大意:给定一个序列 S S S,从中选出任意 n n n个数,可重复、顺序相关,求乘积模 m m m下同余于 x x x的个数,保证 m m m为质数

初看题目,很难看出乘积如何处理。
但它保证了 m m m为质数,就可以联想到离散对数

离散对数链接

将序列 S S S中每个数以及 x x x分别取离散对数,原问题等价成了选出 n n n个数相加,设个数的生成函数为 G ( x ) G(x) G(x)
则: G ( x ) = ( ∑ i = 0 ∞ x i ) n    ( i ∈ S ) G(x)=(\sum_{i=0}^{\infty}x^i)^n\ \ (i\in S) G(x)=(i=0xi)n  (iS)
再用 N T T NTT NTT加速卷积即可
(该题多项式可以直接快速幂,不需要用牛顿迭代)

注意特判 a [ i ] = 0 a[i]=0 a[i]=0!!! ( ( (代码第36行 ) ) )
注意特判 a [ i ] = 0 a[i]=0 a[i]=0!!! ( ( (代码第36行 ) ) )
注意特判 a [ i ] = 0 a[i]=0 a[i]=0!!! ( ( (代码第36行 ) ) )
一个特判一百分。。。

C o d e Code Code

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int MAXM=8000,Mod=1004535809,G=3;
int a[MAXM+10],ind[MAXM+10],tot,f[MAXM<<2|10],invG;
inline int read();

namespace math{
	int PFD(int);
	int find_g(int);
	int fastpow(int,int,int);
}

namespace ntt{
	void get_mod(int*,int);
	void fastpow(int*,int,int);
	void MUL(int*,int*,int,int);
	void NTT(int*,int*,int,int);
}


signed main(){
	//freopen ("std.in","r",stdin);
	//freopen ("std.out","w",stdout);
	int n,m,x,s,g,l;
	n=read(),m=read(),x=read(),s=read();
	g=math::find_g(m);
	l=g;
	for (register int i=1;i<m;++i){
		ind[l]=i%(m-1);
		l=l*g%m;
	}
	x=ind[x];
	for (register int i=1;i<=s;++i){
		a[i]=read();
		if (!a[i])	continue;
		a[i]=ind[a[i]];
		++f[a[i]];
	}
	ntt::fastpow(f,m-2,n);
	printf("%lld\n",f[x]);
	return 0;
}

inline int read(){
	int x=0;
	char c=getchar();
	while (!isdigit(c))c=getchar();
	while (isdigit(c))x=(x<<1)+(x<<3)+(c&15),c=getchar();
	return x;
}

namespace math{
	int PFD(int p,int *prime){
		int tot=0;
		for (register int i=2;p!=1;++i){
			if (p%i==0)	prime[++tot]=i;
			while (p%i==0)	p/=i;
		}
		return tot;
	}
	
	int find_g(int p){
		static int prime[MAXM+10];
		int tot=PFD(p-1,prime),tmp;
		for (register int g=1;g<p;++g){
			tmp=1;
			for (register int i=1;i<=tot;++i){
				if (fastpow(g,(p-1)/prime[i],p)==1){
					tmp=0;
					break;
				}
			}
			if (tmp)	return g;
		}
		return -1;
	}
	
	int fastpow(int x,int p,int mod=Mod){
		int ans=1;
		while (p){
			if (p&1)	ans=ans*x%mod;
			x=x*x%mod;
			p>>=1;
		}
		return ans;
	}
}

namespace ntt{
	void MUL(int *a,int *b,int n,int m){
		static int f[MAXM<<2|10],g[MAXM<<2|10],tree[MAXM<<2|10];
		for (register int i=0;i<=n;++i)	f[i]=a[i]%Mod;
		for (register int i=0;i<=m;++i)	g[i]=b[i]%Mod;
		m+=n; n=1;
		while (n<=m)	n<<=1;
		int invn=math::fastpow(n,Mod-2);	invG=math::fastpow(G,Mod-2);
		for (register int i=1;i<n;++i)	tree[i]= (tree[i>>1]>>1) | ((i&1)? n>>1 : 0);
		NTT(f,tree,n,0),NTT(g,tree,n,0);
		for (register int i=0;i<=n;++i)	f[i]=f[i]*g[i]%Mod;
		NTT(f,tree,n,invn);
		for (register int i=0;i<=m;++i)	a[i]=f[i];
		for (register int i=0;i<=n;++i)	f[i]=g[i]=0;
	}
	
	void NTT(int *f,int *tree,int n,int op){
		for (register int i=0;i<n;++i)
			if (i>tree[i])	swap(f[i],f[tree[i]]);
		
		for (register int p=2;p<=n;p<<=1){
			int len=p>>1;
			int rg=math::fastpow(op? invG : G, (Mod-1)/p);
			for (register int k=0;k<n;k+=p){
				int buf=1;
				for (register int l=k;l<k+len;++l){
					int tmp=buf*f[l+len]%Mod;
					f[l+len]=f[l]-tmp;
					if (f[l+len]<0)	f[l+len]+=Mod;
					f[l]=(f[l]+tmp)%Mod;
					buf=buf*rg%Mod;
				}
			}
		}
		if (op){
			for (register int i=0;i<=n;++i)
				f[i]=f[i]*op%Mod;
		}
	}
	
	void fastpow(int *f,int n,int p){
		static int ans[MAXM<<2|10];
		for (register int i=0;i<=n;++i)	ans[i]=0;
		for (register int i=0;i<=n;++i)	ans[i]=f[i];
		--p;
		while (p){
			if (p&1)	MUL(ans,f,n,n),get_mod(ans,n);
			MUL(f,f,n,n),get_mod(f,n);
			p>>=1;
		}
		for (register int i=0;i<=n;++i)	f[i]=ans[i];
	}
	
	void get_mod(int *f,int n){
		++n;
		for (register int i=n;i<n+n;++i){
			f[i%n]=(f[i%n]+f[i]) %Mod;
			f[i]=0;
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值