给出\(n,m,x,S\),其中\(S\subseteq [0,m)\),问有多少个长度为\(n\)的数列\(a\)使得\(a_i\in S\),并且数列中所有元素的乘积mod \(m\)为\(x\)。

答案对\(1004535809=479\times 2^{21}+1\)取模。\(m\)为质数。\(n\le 10^9,m< 8000\)。

分析

在实数意义下,一个经典的处理乘法的方法是通过取对数转化成加法,例如在计算最小乘积生成树时,我们对边权取对数,转化成一般的最小生成树问题。在模意义下也有类似的巧妙方法,利用模数的原根

可以证明\(1,2,4,p,2p,p^n\)都有原根,而且只有这些数有原根(\(p\)表示奇素数)。原根\(g\)满足\(g^x\equiv 1\)成立当且仅当\(x=p-1\)。这就是说,用\(g\)的幂可以在模意义下表示出\([1,p)\)的所有数。也就是说,如果模数有原根,那么模意义下的乘法可以转化成原根的指数的加法。

令\(x=g^b\),那么问题就变成了,在数集中,选出\(n\)个数,一个数可以选多次,有多少种方法能够让选出来的数的和为\(b\)。

运用生成函数\(f\),那么问题就变成了\(f^n(x)\),注意这里其实是一个\(\mod m-1\)的循环卷积,最后累加回来即可。\(n\)比较大用快速幂。

这题的关键在于利用原根把乘法转化成加法

代码

写这个题主要是想试一下任意模数fft的做法,所以谢了两份代码,第一份是ntt的,第二个是拆模数的fft。

ntt

#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long giant;
const int q=479<<21|1;
const int g=3;
int read() {
int x=0,f=1;
char c=getchar();
for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
for (;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*f;
}
const int maxj=16;
const int maxn=1<<maxj|1;
int a[maxn],b[maxn],c[maxn],mg,M,xj,rev[maxn],n,m,d[maxn];
int Wn[maxj][2];
int sub(int x,int y) {
int ret=((giant)x-y)%q;
if (ret<0) ret+=q;
return ret;
}
int Plus(int x,int y) {
return ((giant)x+y)%q;
}
int multi(int x,int y) {
return (giant)x*y%q;
}
int mi(int x,int y) {
int ret=1;
for (;y;y>>=1,x=multi(x,x)) if (y&1) ret=multi(ret,x);
return ret;
}
int inv(int x) {
return mi(x,q-2);
}
void ntt(int a[],int op=0) {
for (int i=0;i<M;++i) if (i<rev[i]) swap(a[i],a[rev[i]]);
for (int the=1,i=2;i<=M;i<<=1,++the) {
for (int j=0;j<M;j+=i) {
for (int k=j,w=1;k<j+i/2;++k,w=multi(w,Wn[the][op])) {
int u=a[k],v=multi(a[k+i/2],w);
a[k]=Plus(u,v),a[k+i/2]=sub(u,v);
}
}
}
if (op) {
int iv=inv(M);
for (int i=0;i<M;++i) a[i]=multi(a[i],iv);
}
}
void self(int a[]) {
ntt(a);
for (int i=0;i<M;++i) a[i]=multi(a[i],a[i]);
ntt(a,1);
}
void mul(int c[],int a[]) {
ntt(a);
ntt(c);
for (int i=0;i<M;++i) c[i]=multi(c[i],a[i]);
ntt(a,1);
ntt(c,1);
}
void back(int a[]) {
memset(d,0,sizeof d);
for (int i=0;i<M;++i) d[i%(m-1)]=Plus(d[i%(m-1)],a[i]);
memcpy(a,d,(sizeof d[0])*M);
}
void ans(int a[],int len,int y) {
memset(c,0,sizeof c);
c[0]=1;
for (xj=0,M=1;M<=(len<<1);++xj,M<<=1);
for (int i=0;i<M;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(xj-1));
while (y) {
if (y&1) {
mul(c,a);
back(c);
}
y>>=1;
self(a);
back(a);
}
}
int getg(int m) {
for (int i=2;i<m;++i) {
int tmp=i;
bool flag=true;
for (int j=1;j<m;++j,tmp=tmp*i%m) if (tmp==1 && j!=m-1) {
flag=false;
break;
}
if (flag) return i;
}
return 0;
}
int S[maxn];
int main() {
#ifndef ONLINE_JUDGE
freopen("test.in","r",stdin);
freopen("my.out","w",stdout);
#endif
Wn[0][0]=Wn[0][1]=1;
for (int i=1;i<maxj;++i) Wn[i][1]=inv(Wn[i][0]=mi(g,(q-1)>>i));
n=read(),m=read();
int x=read(),s=read();
mg=getg(m);
for (int tmp=1,i=0;i<m-1;++i,tmp=tmp*mg%m) b[tmp]=i;
for (int i=1;i<=s;++i) S[i]=read();
for (int i=1;i<=s;++i) if (S[i]) ++a[b[S[i]]];
ans(a,m,n);
printf("%d\n",c[b[x]]);
return 0;
}

fft

任意模数fft的做法是,设模数为\(p\),把多项式的系数拆成\(a\sqrt p+b\),分成两个多项式\((A,B)\)。

假设\(X=A\sqrt p+B,Y=C\sqrt p+D\),那么进行分步计算:

\[X\times Y=A\times C*(\sqrt p*\sqrt p) +(B\times C+A\times D)*\sqrt p+B\times D \mod p
\]

分开取模,这样就不会受到精度问题影响了。要开long double。

#include<cstdio>
#include<cctype>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long double lb;
int read() {
int x=0,f=1;
char c=getchar();
for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
for (;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*f;
}
typedef long long giant;
const int q=479<<21|1;
const int sq=31695;
const int sqb=(giant)sq*sq%q;
const lb pi=acos(-1.0);
struct C {
lb r,i;
C (lb r=0,lb i=0):r(r),i(i) {}
};
C operator * (C a,C b) {return C(a.r*b.r-a.i*b.i,a.r*b.i+a.i*b.r);}
C operator + (C a,C b) {return C(a.r+b.r,a.i+b.i);}
C operator - (C a,C b) {return C(a.r-b.r,a.i-b.i);}
C operator / (C a,lb x) {return C(a.r/x,a.i/x);}
const int maxj=16;
const int maxn=1<<maxj|1;
int M,a[maxn],c[maxn],xj,rev[maxn],b[maxn];
struct Array {
C a[maxn];
C& operator [] (int x) {return a[x];}
void back(int m) {
for (int i=m-1;i<M;++i) a[i%(m-1)]=a[i%(m-1)]+a[i],a[i]=0;
}
void init(int b[]) {
for (int i=0;i<M;++i) a[i]=b[i];
}
void one() {
for (int i=0;i<M;++i) a[i]=0;
a[0]=1;
}
void fft(int op=1) {
for (int i=0;i<M;++i) if (i<rev[i]) swap(a[i],a[rev[i]]);
for (int i=2;i<=M;i<<=1) {
C wn(cos(2*pi/i),op*sin(2*pi/i));
for (int j=0;j<M;j+=i) {
C w(1);
for (int k=j;k<j+i/2;++k,w=w*wn) {
C u=a[k],v=a[k+i/2]*w;
a[k]=u+v,a[k+i/2]=u-v;
}
}
}
if (op==-1) {
for (int i=0;i<M;++i) a[i]=a[i]/M;
}
}
} A,B,D,E,F,G,H,TA,TC;
Array operator * (Array &a,Array &b) {
Array ret;
a.fft(),b.fft();
for (int i=0;i<M;++i) ret[i]=a[i]*b[i];
a.fft(-1),b.fft(-1),ret.fft(-1);
return ret;
}
Array operator + (Array &a,Array &b) {
Array ret;
for (int i=0;i<M;++i) ret[i]=a[i]+b[i];
return ret;
}
void operator *= (Array &a,Array &b) {
a.fft(),b.fft();
for (int i=0;i<M;++i) a[i]=a[i]*b[i];
a.fft(-1),b.fft(-1);
}
void split(Array &a,Array &c,Array &d) {
for (int i=0;i<M;++i) {
giant x=(giant)(a[i].r+0.5);
giant y=x%sq;
giant z=x/sq;
c[i]=z;
d[i]=y;
}
}
int multi(giant x,giant y) {
return (giant)x*y%q;
}
int Plus(giant x,giant y) {
return ((giant)x+y)%q;
}
Array mul(Array &a,Array &b) {
Array ret;
split(a,A,B);
split(b,D,E);
F=A*D;
G=A*E;
H=B*D;
G=G+H;
H=B*E;
for (int i=0;i<M;++i) {
giant x=(giant)(F[i].r+0.5);
giant y=(giant)(G[i].r+0.5);
giant z=(giant)(H[i].r+0.5);
x=multi(x,sqb);
y=multi(y,sq);
x=Plus(x,Plus(y,z));
ret[i]=x;
}
return ret;
}
void ans(int a[],int m,int y) {
for (xj=0,M=1;M<=(m<<1);M<<=1,++xj);
for (int i=0;i<M;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(xj-1));
TA.init(a);
TC.one();
while (y) {
if (y&1) TC=mul(TC,TA),TC.back(m);
y>>=1;
TA=mul(TA,TA),TA.back(m);
}
for (int i=0;i<M;++i) c[i]=((giant)(TC[i].r+0.5))%q;
}
int getm(int m) {
for (int i=2;i<m;++i) {
bool flag=true;
for (int tmp=i,j=1;j<m;++j,tmp=tmp*i%m) if (tmp==1 && j!=m-1) {
flag=false;
break;
}
if (flag) return i;
}
return 0;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("test.in","r",stdin);
freopen("my.out","w",stdout);
#endif
int n=read(),m=read(),x=read(),s=read();
int mg=getm(m);
for (int tmp=1,i=0;i<m-1;++i,tmp=tmp*mg%m) b[tmp]=i;
for (int i=1,the;i<=s;++i) if ((the=read())!=0) ++a[b[the]];
ans(a,m,n);
printf("%d\n",c[b[x]]);
return 0;
}

bzoj3992-序列统计的更多相关文章

  1. 【BZOJ3992】序列统计(动态规划,NTT)

    [BZOJ3992]序列统计(动态规划,NTT) 题面 BZOJ 题解 最裸的暴力 设\(f[i][j]\)表示前\(i\)个数,积在膜意义下是\(j\)的方案数 转移的话,每次枚举一个数,直接丢进去 ...

  2. [BZOJ3992][SDOI2015]序列统计(DP+原根+NTT)

    3992: [SDOI2015]序列统计 Time Limit: 30 Sec  Memory Limit: 128 MBSubmit: 1888  Solved: 898[Submit][Statu ...

  3. 【BZOJ3992】[SDOI2015]序列统计 NTT+多项式快速幂

    [BZOJ3992][SDOI2015]序列统计 Description 小C有一个集合S,里面的元素都是小于M的非负整数.他用程序编写了一个数列生成器,可以生成一个长度为N的数列,数列中的每个数都属 ...

  4. bzoj3992【SDOI2015】序列统计

    3992: [SDOI2015]序列统计 Time Limit: 30 Sec  Memory Limit: 128 MB Submit: 673  Solved: 327 [Submit][Stat ...

  5. 【SDOI2015】序列统计 解题报告

    2119: [BZOJ3992][SDOI2015]序列统计 Description 小\(C\)有一个集合\(S\),里面的元素都是小于\(M\)的非负整数. 他用程序编写了一个数列生成器,可以生成 ...

  6. Bzoj 4403: 序列统计 Lucas定理,组合数学,数论

    4403: 序列统计 Time Limit: 3 Sec  Memory Limit: 128 MBSubmit: 328  Solved: 162[Submit][Status][Discuss] ...

  7. BZOJ4403 序列统计—Lucas你好

    绝对是全网写的最详细的一篇题解  题目:序列统计 代码难度:简单 思维难度:提高+-省选 讲下题面:给定三个正整数N.L和R,统计长度在1到N之间,元素大小都在L到R之间的单调不降序列的数量.输出答案 ...

  8. BZOJ 3992: [SDOI2015]序列统计 [快速数论变换 生成函数 离散对数]

    3992: [SDOI2015]序列统计 Time Limit: 30 Sec  Memory Limit: 128 MBSubmit: 1017  Solved: 466[Submit][Statu ...

  9. [SDOI2015]序列统计

    [SDOI2015]序列统计 标签: NTT 快速幂 Description 给你一个模m意义下的数集,需要用这个数集生成一个数列,使得这个数列在的乘积为x. 问方案数模\(1004535809\). ...

  10. 【BZOJ4403】序列统计(组合数学,卢卡斯定理)

    [BZOJ4403]序列统计(组合数学,卢卡斯定理) 题面 Description 给定三个正整数N.L和R,统计长度在1到N之间,元素大小都在L到R之间的单调不降序列的数量.输出答案对10^6+3取 ...

随机推荐

  1. Shr-前端汇总

    F7控件监听 f7控件的监听是指,在点击F7控件时,对控件内的内容进行选中后出发该事件监听:通过参数value可以获取当前F7控件的一些值信息. //人力编制方案监听 回写计划期间 及分录数据 ini ...

  2. CF 480 E. Parking Lot

    CF 480 E. Parking Lot http://codeforces.com/contest/480/problem/E 题意: 给一个n*m的01矩阵,每次可以将一个0修改为1,求最大全0 ...

  3. kafka配置参数详解

    Broker  Configs Property Default Description broker.id   每个broker都可以用一个唯一的非负整数id进行标识:这个id可以作为broker的 ...

  4. SpringBoot-01:什么是SpringBoot?

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- SpringBoot: Spring Boot可以轻松创建独立的,生产级的基于Spring的应用程序,您可以“ ...

  5. YUM本地源制作与yum网络版仓库

    1.修改本机上的YUM源配置文件,将源指向自己 cd /etc/yum.repos.d/ 备份原有的YUM源的配置文件 rename .repo .repo.bak * rename CentOS-M ...

  6. bash脚本练习

    练习一: 1.添加5个用户,user1,...,user5: 2.每个用户的密码同用户名,添加密码完成后,不显示命令的执行结果: 3.每个用户添加完成后,都要显示用户某某已添加成功. useradd ...

  7. jmeter 函数助手

    1.选项,函数助手对话框,打开函数助手 2.使用方法 输入参数,点击生成,可以直接使用(Name of variable in which to store the result (optional) ...

  8. python操作符及其循环语句(非常全)

    //2018.10.14 1. Windows + R可以直接进行运行cmd 2. Random.randint(a,b):产生a-b的任意一个整数,在IDLE里面运行时需要注意在前面写好调用impo ...

  9. 关于html2canvas清晰度

    最近有个小项目 需要生成海报让用户去分享~~~vue做的,海报通过html2canvas 生成. 遇到的最大问题是生成图片的清晰度~~网上找了好多方法. 放大倍数!~网上找的~~ var cntEle ...

  10. 单词 (Play on Words UVA - 10129 )

    题目描述: 原题:https://vjudge.net/problem/UVA-10129 题目思路: 1.明显是判断欧拉路径 2.欧拉路径的两个条件 a.图连通 b.至多为两个奇点,且一个为起点一个 ...