\(\text{Description}\)

传送门

\(\text{Solution}\)

首先能想到朴素 \(\mathtt{dp}\):定义 \(f(i)\) 为元素 \(i\) 在序列中的组成方案数,那么相当于进行 \(n-1\) 次迭代:

\[f_n(i)=\sum_{jk=i}f_{n-1}(j)\cdot f(k) \]

每次迭代之后将 \(>m\) 的部分叠到前面去。

事实上我们可以利用倍增来优化,就是把数与数列合并变成数列和数列合并,可以做到 \(\mathcal O(m^2\log n)\).

合并的形式其实与卷积十分相似,不妨考虑将乘法转换成加法。经典的转换方式就是取对数,如果在模意义下,相当于是 \(a^x\rightarrow x\). 另外还有一个重要的性质需要保证:\(a^x\)\(x\) 是一一对应的关系。

所以不妨用 \(m\) 的原根(注意 \(m\) 是质数)来充作这个 \(a\). 不过此时可用的指数范围为 \([0,\varphi(m))\),只有 \(m-2\) 个数,但是显然 \(0\) 取不了对数(且 \(0\) 对答案也没有贡献),所以可以放心飞。需要注意的是此时下标范围为 \([0,\varphi(m))\),相当于对 \(\varphi(m)\) 取模。

\(\mathtt{NTT}\) 计算即可做到 \(\mathcal O(m\log m\log n)\).

\(\text{Code}\)

#include <cstdio>
#define print(x,y) write(x),putchar(y)

template <class T>
inline T read(const T sample) {
    T x=0; char s; bool f=0;
    while((s=getchar())>'9' || s<'0')
        f |= (s=='-');
    while(s>='0' && s<='9')
        x = (x<<1)+(x<<3)+(s^48),
        s = getchar();
    return f?-x:x;
}

template <class T>
inline void write(T x) {
    static int writ[50],w_tp=0;
    if(x<0) putchar('-'),x=-x;
    do writ[++w_tp]=x-x/10*10,x/=10; while(x);
    while(putchar(writ[w_tp--]^48),w_tp);
}

#include <iostream>
using namespace std;

const int maxn = 8005;
const int mod = 1004535809;
const int ig = 334845270;

int n,m,rt,que,phi,inv,lim,bit;
int f[maxn<<2],g[maxn<<2],ref[maxn],rev[maxn<<2];

int qkpow(int x,int y,int mod) {
	int r=1;
	while(y) {
		if(y&1) r=1ll*r*x%mod;
		x=1ll*x*x%mod; y>>=1;
	}
	return r;
}

void getRoot() {
	for(rt=2; ; ++rt) {
		int x=phi; bool ban = false;
		for(int i=2; i*i<=x; ++i) {
			if(x%i) continue;
			while(x%i==0) x/=i;
			if(qkpow(rt,phi/i,m)==1) {
				ban = true; break;
			}
		}
		if(x>1 && qkpow(rt,phi/x,m)==1) ban=true;
		if(!ban) return;
	}
}


void preWork() {
	lim=1;
	while(lim<phi*2) lim<<=1, ++bit;
	inv = qkpow(lim,mod-2,mod);
	for(int i=0;i<lim;++i)
		rev[i] = (rev[i>>1]>>1)|((i&1)<<bit-1);
}

inline void inc(int &x,int y) {
	x = (x+y>=mod?x+y-mod:x+y);
}

inline int dec(int x,int y) {
	return x-y<0?x-y+mod:x-y;
}

void NTT(int *f,bool opt=1) {
	for(int i=0;i<lim;++i)
		if(i<rev[i]) swap(f[i],f[rev[i]]);
	int tmp,wn,w;
	for(int mid=1;mid<lim;mid<<=1) {
		wn = qkpow(opt?3:ig,(mod-1)/(mid<<1),mod);
		for(int i=0;i<lim;i+=(mid<<1)) {
			w=1;
			for(int j=0;j<mid;++j,w=1ll*w*wn%mod) {
				tmp = 1ll*f[i|j|mid]*w%mod;
				f[i|j|mid] = dec(f[i|j],tmp);
				inc(f[i|j],tmp);
			}
		}
	}
	if(!opt) for(int i=0;i<lim;++i) f[i]=1ll*f[i]*inv%mod;
} 

void double_it() {
	g[0]=1;
	while(n) {
		if(n&1) {
			NTT(f), NTT(g);
			for(int i=0;i<lim;++i)
				g[i] = 1ll*f[i]*g[i]%mod;
			NTT(f,0), NTT(g,0);
			for(int i=phi;i<lim;++i)
				inc(g[i%phi],g[i]), g[i]=0;
		}
		n>>=1; NTT(f);
		for(int i=0;i<lim;++i)
			f[i] = 1ll*f[i]*f[i]%mod;
		NTT(f,0);
		for(int i=phi;i<lim;++i)
			inc(f[i%phi],f[i]), f[i]=0;
	}
}

int main() {
	n=read(9),m=read(9),que=read(9);
	phi = m-1; getRoot(); 
	for(int i=0;i<phi;++i) 
		ref[qkpow(rt,i,m)] = i;
	for(int T=read(9); T; --T) {
		int x=read(9);
		if(x) ++ f[ref[x]];
	}
	preWork(); double_it();
	print(g[ref[que]],'\n');
	return 0;
}
posted on 2021-02-03 11:48  Oxide  阅读(55)  评论(0编辑  收藏  举报