给出\(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. ORB-SLAM(十一)EPnP

    EPnP在ORB-SLAM中主要用于Tracking线程中的重定位Relocalization模块,需要通过当前关键帧Bow与候选帧匹配上的3D地图点,迅速建立当前相机的初始姿态. PnP问题解决了已 ...

  2. Servlet处理文件下载的编码问题,乱码。

    Servlet处理文件下载的编码问题,乱码. //处理文件名乱码问题 // 获得请求头中的User-Agent String agent = request.getHeader("User- ...

  3. apache+php+mysql开发环境搭建

    一.Apache       因为Apache官网只提供源代码,如果要使用必须得自己编译,这里我选择第三方安装包Apache Lounge. 进入Apachelounge官方下载地址:http://w ...

  4. Linux命令应用大词典-第8章 日期和时间

    8.1 cal:显示日历信息 8.2 date:显示和设置系统日期和时间 8.3 hwclock:查看和设置硬件时钟 8.4 clock:查看和设置硬件时钟 8.5 clockdiff:主机之间测量时 ...

  5. spark操作数据库的几种方法

    一.使用jdbcRDD的接口: SparkConf conf = new SparkConf(); conf.setAppName("Simple Application").se ...

  6. LeetCode 144 ——二叉树的前序遍历

    1. 题目 2. 解答 2.1. 递归法 定义一个存放树中数据的向量 data,从根节点开始,如果节点不为空,那么 将当前节点的数值加入到 data 中 递归得到其左子树的数据向量 temp,将 te ...

  7. [Data Structures and Algorithms - 1] Introduction & Mathematics

    References: 1. Stanford University CS97SI by Jaehyun Park 2. Introduction to Algorithms 3. Kuangbin' ...

  8. JQuery文本框验证

    <" CODEPAGE="936"%><!--#include file="conncon.asp"--><!--#in ...

  9. ThinkPHP - 3 - IDE选择以及Eclipse PDT打开ThinkPHP项目

    ThinkPHP框架已部署到SAE(新浪云),且代码已获取到本地.眼前面临的问题就是,对ThinkPHP项目选择哪种开发工具(IDE)? 经过简单的查找比较,以及电脑里已装有Eclipse的因素,遂决 ...

  10. [leetcode-663-Equal Tree Partition]

    Given a binary tree with n nodes, your task is to check if it's possible to partition the tree to tw ...