[BZOJ 3992] [SDOI 2015] 序列统计(DP+原根+NTT)

题面

小C有一个集合S,里面的元素都是小于质数M的非负整数。他用程序编写了一个数列生成器,可以生成一个长度为N的数列,数列中的每个数都属于集合S。小C用这个生成器生成了许多这样的数列。但是小C有一个问题需要你的帮助:给定整数x,求所有可以生成出的,且满足数列中所有数的乘积mod M的值等于x的不同的数列的有多少个。小C认为,两个数列{Ai}和{Bi}不同,当且仅当至少存在一个整数i,满足Ai≠Bi。另外,小C认为这个问题的答案可能很大,因此他只需要你帮助他求出答案mod 1004535809的值就可以了。

分析

设\(dp[i][j]\)表示长度为i,所有数的乘积modM的值为j的序列个数,那么可以写出转移方程:

\[dp[2 \cdot i][j]=\sum_{ab=j(\mathrm{mod}\ m)} dp[i][a] \cdot dp[i][b]
\]

发现乘法不好处理,如果没有模意义,直接取个对数\(\ln a+ \ln b =\ln j\),就是卷积了

定理: 假设g是奇素数p的一个原根,则$g1,g2 \cdot g^{p-1} $在模p意义下两两不同,且结果恰好为1~p-1

那么我们可以把\(a,b\)用原根表示,\(a=g^x,b=g^y,j=g^z\).这里的\(x,y\)实际上就是\(a,b\)的离散对数.关于离散对数的严格定义,这里不再赘述,只需要感性理解即可.

那么\(g^x g^y \equiv g^z (\mathrm{mod}\ m)\)

\(\therefore g^{x+y} \equiv g^z (\mathrm{mod}\ m)\)

因为\(m\)是质数,根据费马小定理\(g^{m-1} \equiv 1 (\mathrm{mod}\ m)\)

因此\(g^{x+y} \equiv g^{(x+y) \mod (m-1)} (\mathrm{mod}\ m)\)

因此\(x+y \equiv z (\mathrm{mod}\ m-1)\)

这样就可以把式子转化成卷积。注意到每一层的转移是一样的,可以用类似快速幂的方法,进行多项式快速幂.

void poly_pow(ll *x,ll *ans,int k,int sz){
ans[0]=1;
while(k){
if(k&1) mul(ans,x,ans,sz);
mul(x,x,x,sz);
k>>=1;
}
}

还要注意每次要把卷积结果中指数大于m-1的项移到前面

NTT(tmpa,sz,1);
NTT(tmpb,sz,1);
for(int i=0;i<sz;i++) ans[i]=tmpa[i]*tmpb[i]%mod;
NTT(ans,sz,-1);
for(int i=sz-1;i>=m-1;i--){
ans[i%(m-1)]+=ans[i];
ans[i%(m-1)]%=mod;
ans[i]=0;//注意指数取模m-1
}

最后还要一个问题,我们要求出输入的数m的原根.

由于原根一般很小,求一个数m的原根可以暴力枚举。

对\(\varphi(n)\)素因子分解,设\(\varphi(n)=\Pi_{i=1}^{k}{p_i}^{a_i}, p为质数\)

从小到大枚举\(g\),若\(\forall p_i,\ g^{\frac{\varphi(n)}{p_i}} \ne 1(mod \ n)\),则\(g\)就是\(n\)的原根

inline int get_root(int x){
static ll p[maxn+5];//分解质因数
int cnt=0;
ll phi=x-1;
for(int i=2;phi!=1;i++){
if(phi%i==0) p[++cnt]=i;
while(phi%i==0) phi/=i;
}
for(int i=2;;i++){
bool flag=1;
for(int j=1;j<=cnt;j++){
if(fast_pow(i,(x-1)/p[j],x)==1){//注意是模m不是模mod
flag=0;
break;
}
}
if(flag) return i;
}
return -1;
}

在代码实现中,需小心当前过程到底要取模哪个模数,本题里有3个模数.

  • 乘积的时候模m
  • 离散对数求和模m-1
  • 方案数模1004535809

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define maxn 300000
#define G 3
#define mod 1004535809
#define invG 334845270
using namespace std;
typedef long long ll;
inline ll fast_pow(ll x,ll k,ll m){
ll ans=1;
while(k){
if(k&1) ans=ans*x%m;
x=x*x%m;
k>>=1;
}
return ans;
}
inline ll inv(ll x,ll m){
return fast_pow(x,m-2,m);
} void NTT(ll *x,int n,int type){
static int rev[maxn+5];
int tn=1,k=0;
while(tn<n){
tn*=2;
k++;
}
for(int i=0;i<n;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(k-1));
for(int i=0;i<n;i++) if(i<rev[i]) swap(x[i],x[rev[i]]);
for(int len=1;len<n;len*=2){
int sz=len*2;
ll gn1=fast_pow((type==1?G:invG),(mod-1)/sz,mod);
for(int l=0;l<n;l+=sz){
int r=l+len-1;
ll gnk=1;
for(int i=l;i<=r;i++){
ll tmp=x[i+len];
x[i+len]=(x[i]-gnk*tmp%mod+mod)%mod;
x[i]=(x[i]+gnk*tmp%mod)%mod;
gnk=gnk*gn1%mod;
}
}
}
if(type==-1){
ll invn=inv(n,mod);
for(int i=0;i<n;i++) x[i]=x[i]*invn%mod;
}
} int n,m,C,S,g;
int a[maxn+5];
int lg[maxn+5];
ll f[maxn+5],ans[maxn+5];
inline int get_root(int x){
static ll p[maxn+5];//分解质因数
int cnt=0;
ll phi=x-1;
for(int i=2;phi!=1;i++){
if(phi%i==0) p[++cnt]=i;
while(phi%i==0) phi/=i;
}
for(int i=2;;i++){
bool flag=1;
for(int j=1;j<=cnt;j++){
if(fast_pow(i,(x-1)/p[j],x)==1){//注意是模m不是模mod
flag=0;
break;
}
}
if(flag) return i;
}
return -1;
} void mul(ll *a,ll *b,ll *ans,int sz){
static ll tmpa[maxn+5],tmpb[maxn+5];
for(int i=0;i<m;i++){
tmpa[i]=a[i];
tmpb[i]=b[i];
}
for(int i=m;i<sz;i++) tmpa[i]=tmpb[i]=0;
NTT(tmpa,sz,1);
NTT(tmpb,sz,1);
for(int i=0;i<sz;i++) ans[i]=tmpa[i]*tmpb[i]%mod;
NTT(ans,sz,-1);
for(int i=sz-1;i>=m-1;i--){
ans[i%(m-1)]+=ans[i];
ans[i%(m-1)]%=mod;
ans[i]=0;//注意指数取模m-1
}
}
void poly_pow(ll *x,ll *ans,int k,int sz){
ans[0]=1;
while(k){
if(k&1) mul(ans,x,ans,sz);
mul(x,x,x,sz);
k>>=1;
}
} int main(){
#ifdef LOCAL
freopen("1.in","r",stdin);
#endif
scanf("%d %d %d %d",&n,&m,&C,&S);
g=get_root(m);
for(int i=0,x=1;i<m-1;i++){
lg[x]=i;
x=x*g%m;
}
for(int i=1;i<=S;i++){
scanf("%d",&a[i]);
a[i]%=m;
if(a[i]!=0) f[lg[a[i]]]=1;
}
int dn=1;
while(dn<=2*(m-1)) dn*=2;
poly_pow(f,ans,n,dn);
printf("%lld\n",ans[lg[C]]);
}

[BZOJ 3992] [SDOI 2015] 序列统计(DP+原根+NTT)的更多相关文章

  1. BZOJ 3992 [SDOI 2015] 序列统计 解题报告

    这个题最暴力的搞法就是这样的: 设 $Dp[i][j]$ 为前 $i$ 个数乘积为 $j$ 的方案数. 转移的话就不多说了哈... 当前复杂度 $O(nm^2)$ 注意到,$M$ 是个质数,就说明 $ ...

  2. [BZOJ 3992] [SDOI 2015] 序列统计

    Description 传送门 Solution [一] 设 \(f[i][j]\) 表示前 \(i\) 个数的乘积在模 \(p\) 意义下等于 \(j\) 的方案数,有 \[ f[i][j]=\su ...

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

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

  4. [SDOI 2015]序列统计

    Description 题库链接 给出集合 \(S\) ,元素都是小于 \(M\) 的非负整数.问能够生成出多少个长度为 \(N\) 的数列 \(A\) ,数列中的每个数都属于集合 \(S\) ,并且 ...

  5. BZOJ 3992: [SDOI2015]序列统计 快速幂+NTT(离散对数下)

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

  6. 【BZOJ 4403】 4403: 序列统计 (卢卡斯定理)

    4403: 序列统计 Time Limit: 3 Sec  Memory Limit: 128 MBSubmit: 653  Solved: 320 Description 给定三个正整数N.L和R, ...

  7. BZOJ.3992.[SDOI2015]序列统计(DP NTT 原根)

    题目链接 \(Description\) 给定\(n,m,x\)和集合\(S\).求\(\prod_{i=1}^na_i\equiv x\ (mod\ m)\)的方案数.其中\(a_i\in S\). ...

  8. bzoj 3992: [SDOI2015]序列统计【原根+生成函数+NTT+快速幂】

    还是没有理解透原根--题目提示其实挺明显的,M是质数,然后1<=x<=M-1 这种计数就容易想到生成函数,但是生成函数是加法,而这里是乘法,所以要想办法变成加法 首先因为0和任何数乘都是0 ...

  9. BZOJ 1046: [HAOI2007]上升序列 LIS -dp

    1046: [HAOI2007]上升序列 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 3438  Solved: 1171[Submit][Stat ...

随机推荐

  1. JAVA中的23种设计模式

    http://blog.csdn.net/chmask/article/details/2631485 http://www.cnblogs.com/hnrainll/archive/2011/12/ ...

  2. SpringBoot+Rocketmq

    @PostConstruct:用于在依赖关系注入完成之后需要执行的方法上,以执行任何初始化.此方法必须在将类放入服务之前调用. @PreDestroy:在开发中我们如果要在关闭spring容器后释放一 ...

  3. python语言特性简要记载

    1.python是解释型语言,而c,c++等是编译型语言. 2.python是动态类型语言,这意味着你不需要在声明变量时指定类型. 3.Python是面向对象语言,所有允许定义类并且可以继承和组合.P ...

  4. windows如何禁用惹人烦的开机启动广告

    本地组策略编辑器 建立新的路径规则 重启电脑 本地组策略编辑器 你现在还在为那些烦人的互联网开机广告而发愁嘛,比如一下几种广告:这样的 还是这样的: 又或者是这样的: 修改了dns也并没有什么卵用,所 ...

  5. 创建ThreadFactory实例的多种方式

    spring的CustomizableThreadFactory guava的MoreExecutors.platformThreadFactory()静态方法 guava的ThreadFactory ...

  6. 解决html5大文件断点续传

    一.概述 所谓断点续传,其实只是指下载,也就是要从文件已经下载的地方开始继续下载.在以前版本的HTTP协议是不支持断点的,HTTP/1.1开始就支持了.一般断点下载时才用到Range和Content- ...

  7. 文件操作:rewind()

    函数名: rewind() 功 能: 将文件内部的位置指针重新指向一个流(数据流/文件)的开头   注意:不是文件指针而是文件内部的位置指针,随着对文件的读写文件的位置指针(指向当前读写字节)向后移动 ...

  8. noip模拟题 Market

    题面描述: 数据范围: Solution: 我们发现\(v\)很小,但是\(M\)很大,考虑转化一下一般的背包 我们用\(f[v]\)来表示拿到价值为\(v\)的物品需要付出的最少代价,特别的,当\( ...

  9. OI多项式 简单学习笔记

    咕咕咕 先开个坑(其实是存模板来了) 一些特别简单的前置东西qwq 复数的计算 复数相加:向量相加,复数相乘.复数相乘:模长相乘,旋转量相加(就是复平面坐标轴逆时针旋转的角度) (当然也可以直接使用c ...

  10. ArrayList,Vector ,LinkedList的存储性能和特性

    ArrayList,Vector,LinkedList : 两者都采用数组元素方式存储数据,此数组元素数大于实际存储的数据(以便于增加和插入元素),允许直接按照序号索引元素,但是插入元素涉及数组元素移 ...