题目大意

  求多项式 \(\prod_{i=1}^n(x+i)\) 的系数在模 \(p\) 意义下的分布,对 \(998244353\) 取模。

  \(p\) 为质数。

  \(n\leq {10}^{18},p\leq 250000\)

题解

  我们只计算 \([1,p-1]\) 的分布,最后再算出 \(0\) 的出现次数。

  记 \(n1=\lfloor\frac{n}{p}\rfloor,n2=n\bmod p\)。若 \(n\bmod p=p-1\),则 \(n1=\lfloor\frac{n}{p}\rfloor+1,n2=0\)。

\[\begin{align}
&\prod_{i=0}^n(x+i)\\
=&x^{n1}\prod_{i=1}^{p-1}(x+i)^{n1}\prod_{i=1}^{n2}(x+i)
\end{align}
\]


  第一部分我们直接忽略。


  对于第二部分,\(\prod_{i=1}^{p-1}(x+i)=x^{p-1}-1\)(因为左右两边的根都是 \(1,2,\ldots,p-1\))。那么 \(x^i(p-1)\) 项的系数就是 \(\binom{n1}{i}{(-1)}^{n1-i}\)。

  因为系数的模数为 \(p\),所以可以用 lucas 定理。

  \(\binom{x}{y}\equiv\prod_i\binom{x_i}{y_i}\pmod p\)

  假设 \(n1=(a_1a_2\ldots a_m)_p\)。

  对每一位构造一个多项式 \(A(x)\),\(a_i\) 表示有多少种方案满足这一位的贡献是 \(i\)。

  那么可以像数位DP一样,枚举 \(i\) 的前多少位和 \(n1\) 是相同的,然后求出后面的方案数。

  因为每一位都不能超过 \(a_i\),所以直接把每一位对应的多项式乘在一起就好了。

  这里的卷积稍微有点不同,\(a_i=\sum_{j}\sum_{k}[jk\bmod p=i]b_jc_k\),只需要把下标取离散对数就好了。

  不要忘记处理 \({(-1)}^{n1-i}\)。

  复杂度为 \(O(p\log_pn\log p)=O(p\log n)\)。


  对于第三部分,可以用倍增+任意模数FFT求出整个多项式。

  大概就是记 \(F(n,x)=\prod_{i=1}^n(x+i)\),那么 \(F(2n+1,x)=F(2n,x)(x+2n+1)\),\(F(2n,x)=F(n,x)F(n,x+n)\)。

  记 \(F(n,x)=\sum_ia_ix^i\),那么 \(F(n,x+n)=\sum_ia_i(x+n)^i=\sum_ia_i\sum_{j\leq i}x^jn^{i-j}\binom{i}{j}=\sum_{i}x_i\sum_{j\geq i}a_jn^{j-i}\binom{j}{i}\),直接卷积即可。

  复杂度为 \(O(p\log p)\)。


  由于第二部分的多项式形如 \(\sum_ia_ix^{i(p-1)}\),而第三部分的次数 \(<p-1\),所以两部分的乘积不互相干扰,可以分开求出后再合并到一起。

  总时间复杂度为 \(O(p(\log n+\log p))\)。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<functional>
#include<cmath>
#include<vector>
#include<assert.h>
//using namespace std;
using std::min;
using std::max;
using std::swap;
using std::sort;
using std::reverse;
using std::random_shuffle;
using std::lower_bound;
using std::upper_bound;
using std::unique;
using std::vector;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef std::pair<int,int> pii;
typedef std::pair<ll,ll> pll;
void open(const char *s){
#ifndef ONLINE_JUDGE
char str[100];sprintf(str,"%s.in",s);freopen(str,"r",stdin);sprintf(str,"%s.out",s);freopen(str,"w",stdout);
#endif
}
void open2(const char *s){
#ifdef DEBUG
char str[100];sprintf(str,"%s.in",s);freopen(str,"r",stdin);sprintf(str,"%s.out",s);freopen(str,"w",stdout);
#endif
}
int rd(){int s=0,c,b=0;while(((c=getchar())<'0'||c>'9')&&c!='-');if(c=='-'){c=getchar();b=1;}do{s=s*10+c-'0';}while((c=getchar())>='0'&&c<='9');return b?-s:s;}
void put(int x){if(!x){putchar('0');return;}static int c[20];int t=0;while(x){c[++t]=x%10;x/=10;}while(t)putchar(c[t--]+'0');}
int upmin(int &a,int b){if(b<a){a=b;return 1;}return 0;}
int upmax(int &a,int b){if(b>a){a=b;return 1;}return 0;}
const ll p1=998244353;
const int N=530000;
const db pi=acos(-1);
ll fp(ll a,ll b,ll p)
{
ll s=1;
for(;b;b>>=1,a=a*a%p)
if(b&1)
s=s*a%p;
return s;
}
ll n,p,g;
ll n1,n2;
ll e1[N],e2[N];
namespace fft
{
struct cp
{
db x,y;
cp(db a=0,db b=0):x(a),y(b){}
};
cp operator +(cp a,cp b){return cp(a.x+b.x,a.y+b.y);}
cp operator -(cp a,cp b){return cp(a.x-b.x,a.y-b.y);}
cp operator *(cp a,cp b){return cp(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
cp operator /(cp a,int b){return cp(a.x/b,a.y/b);}
cp muli(cp a){return cp(-a.y,a.x);}
cp divi(cp a){return cp(a.y,-a.x);}
cp conj(cp a){return cp(a.x,-a.y);}
const int W=524288;
cp w[N];
int rev[N];
void init()
{
for(int i=0;i<W/2;i++)
w[i]=cp(cos(2*pi/W*i),sin(2*pi/W*i));
}
void fft(cp *a,int n,int t)
{
for(int i=1;i<n;i++)
{
rev[i]=(rev[i>>1]>>1)|(i&1?n>>1:0);
if(rev[i]>i)
swap(a[i],a[rev[i]]);
}
for(int i=2;i<=n;i<<=1)
for(int j=0;j<n;j+=i)
for(int k=0;k<i/2;k++)
{
cp u=a[j+k];
cp v=a[j+k+i/2]*w[W/i*k];
a[j+k]=u+v;
a[j+k+i/2]=u-v;
}
if(t==-1)
{
reverse(a+1,a+n);
for(int i=0;i<n;i++)
a[i]=a[i]/n;
}
}
void fft(db *a,db *b,cp *c,cp *d,int n)
{
static cp a1[N],a2[N];
for(int i=0;i<n;i++)
a1[i]=cp(a[i],b[i]);
fft(a1,n,1);
for(int i=0;i<n;i++)
a2[i]=conj(a1[i]);
reverse(a2+1,a2+n);
for(int i=0;i<n;i++)
{
c[i]=(a1[i]+a2[i])/2;
d[i]=divi(a1[i]-a2[i])/2;
}
}
void ifft(db *a,db *b,cp *c,cp *d,int n)
{
static cp a1[N];
for(int i=0;i<n;i++)
a1[i]=c[i]+muli(d[i]);
fft(a1,n,-1);
for(int i=0;i<n;i++)
{
a[i]=a1[i].x;
b[i]=a1[i].y;
}
}
void mul(ll *a,ll *b,ll *c,int n,int m,int l,ll p)
{
const static int M=1<<16;
static db a1[N],a2[N],b1[N],b2[N],c1[N],c2[N],d1[N],d2[N];
static cp a3[N],a4[N],b3[N],b4[N],c3[N],c4[N],d3[N],d4[N];
int k=1;
while(k<=n+m)
k<<=1;
for(int i=0;i<k;i++)
a1[i]=a2[i]=b1[i]=b2[i]=0;
for(int i=0;i<=n;i++)
{
a[i]=(a[i]+p)%p;
a1[i]=a[i]/M;
a2[i]=a[i]%M;
}
for(int i=0;i<=m;i++)
{
b[i]=(b[i]+p)%p;
b1[i]=b[i]/M;
b2[i]=b[i]%M;
}
fft(a1,a2,a3,a4,k);
fft(b1,b2,b3,b4,k);
for(int i=0;i<k;i++)
{
c3[i]=a3[i]*b3[i];
c4[i]=a3[i]*b4[i];
d3[i]=a4[i]*b3[i];
d4[i]=a4[i]*b4[i];
}
ifft(c1,c2,c3,c4,k);
ifft(d1,d2,d3,d4,k);
for(int i=0;i<=l;i++)
c[i]=((ll)(c1[i]+0.5)%p*M%p*M%p+(ll)(c2[i]+0.5)%p*M%p+(ll)(d1[i]+0.5)%p*M%p+(ll)(d2[i]+0.5)%p)%p;
}
void mul2(ll *a,ll *b,ll *c,int n,ll p)
{
static ll a1[N],a2[N],a3[N];
for(int i=1;i<n;i++)
{
a1[e2[i]]=a[i];
a2[e2[i]]=b[i];
}
mul(a1,a2,a3,n-2,n-2,2*n-4,p);
for(int i=1;i<n;i++)
c[i]=0;
for(int i=0;i<=2*n-4;i++)
c[e1[i%(n-1)]]=(c[e1[i%(n-1)]]+a3[i])%p;
}
}
ll inv[N],fac[N],ifac[N];
void init()
{
inv[1]=fac[0]=fac[1]=ifac[0]=ifac[1]=1;
for(int i=2;i<p;i++)
{
inv[i]=(-p/i*inv[p%i]%p+p)%p;
fac[i]=fac[i-1]*i%p;
ifac[i]=ifac[i-1]*inv[i]%p;
}
}
ll binom(int x,int y)
{
return x>=y&&y>=0?fac[x]*ifac[y]%p*ifac[x-y]%p:0;
}
int check()
{
ll s=1;
for(int i=1;i<=p;i++)
{
s=s*g%p;
if(s==1)
{
if(i==p-1)
return 1;
return 0;
}
}
return 0;
}
void getg()
{
for(g=1;;g++)
if(check())
break;
e1[0]=1;
for(int i=1;i<p-1;i++)
e1[i]=e1[i-1]*g%p;
for(int i=0;i<p-1;i++)
e2[e1[i]]=i;
}
namespace gao1
{
ll ans[N];
ll s[N];
void solve(int n)
{
if(!n)
{
s[0]=1;
return;
}
if(n&1)
{
solve(n-1);
for(int i=n-1;i>=0;i--)
{
s[i+1]=(s[i+1]+s[i])%p;
s[i]=s[i]*n%p;
}
}
else
{
solve(n>>1);
static ll a1[N],a2[N],a3[N];
for(int i=0;i<=n>>1;i++)
{
a1[i]=fp((n>>1),i,p)*ifac[i]%p;
a2[i]=s[i]*fac[i]%p;
}
reverse(a2,a2+(n>>1)+1);
fft::mul(a1,a2,a3,n>>1,n>>1,n>>1,p);
reverse(a3,a3+(n>>1)+1);
for(int i=0;i<=n>>1;i++)
a3[i]=a3[i]*ifac[i]%p;
fft::mul(s,a3,s,n>>1,n>>1,n,p);
}
}
void gao()
{
solve(n2);
for(int i=0;i<=n2;i++)
if(s[i])
ans[s[i]]++;
}
}
namespace gao2
{
ll ans[N];
int a[N],t;
ll s[N];
int v[N];
void gao()
{
static ll a1[N],a2[N];
for(ll _=n1;_;)
{
a[++t]=_%p;
_/=p;
}
ans[1]=1;
for(int j=1;j<=t;j++)
{
v[j]=(p==2&&j>1?1:-1);
for(int i=0;i<p;i++)
a1[i]=0;
for(int i=0;i<=a[j];i++)
a1[(binom(a[j],i)*(i&1?v[j]:1)%p+p)%p]++;
fft::mul2(ans,a1,a2,p,p1);
for(int i=1;i<p;i++)
ans[i]=a2[i];
}
}
}
ll ans[N];
int main()
{
open("b");
fft::init();
scanf("%lld%lld",&n,&p);
init();
getg();
n1=n/p,n2=n%p;
if(n2==p-1)
{
n1++;
n2=0;
}
gao1::gao();
gao2::gao();
fft::mul2(gao1::ans,gao2::ans,ans,p,p1);
ans[0]=(n+1)%p1;
for(int i=1;i<p;i++)
ans[0]=(ans[0]-ans[i])%p1;
for(int i=0;i<p;i++)
{
ans[i]=(ans[i]+p1)%p1;
printf("%lld\n",ans[i]);
}
return 0;
}

【集训队作业2018】取名字太难了 任意模数FFT的更多相关文章

  1. UOJ #449. 【集训队作业2018】喂鸽子

    UOJ #449. [集训队作业2018]喂鸽子 小Z是养鸽子的人.一天,小Z给鸽子们喂玉米吃.一共有n只鸽子,小Z每秒会等概率选择一只鸽子并给他一粒玉米.一只鸽子饱了当且仅当它吃了的玉米粒数量\(≥ ...

  2. [UOJ422][集训队作业2018]小Z的礼物——轮廓线DP+min-max容斥

    题目链接: [集训队作业2018]小Z的礼物 题目要求的就是最后一个喜欢的物品的期望得到时间. 根据$min-max$容斥可以知道$E(max(S))=\sum\limits_{T\subseteq ...

  3. 【UOJ#450】【集训队作业2018】复读机(生成函数,单位根反演)

    [UOJ#450][集训队作业2018]复读机(生成函数,单位根反演) 题面 UOJ 题解 似乎是\(\mbox{Anson}\)爷的题. \(d=1\)的时候,随便怎么都行,答案就是\(k^n\). ...

  4. 【UOJ#422】【集训队作业2018】小Z的礼物(min-max容斥,轮廓线dp)

    [UOJ#422][集训队作业2018]小Z的礼物(min-max容斥,轮廓线dp) 题面 UOJ 题解 毒瘤xzy,怎么能搬这种题当做WC模拟题QwQ 一开始开错题了,根本就不会做. 后来发现是每次 ...

  5. UOJ#418. 【集训队作业2018】三角形

    #418. [集训队作业2018]三角形 和三角形没有关系 只要知道儿子放置的顺序,就可以直接模拟了 记录历史最大值 用一个pair(a,b):之后加上a个,期间最大值为增加b个 合并? A1+A2= ...

  6. 2019.2.25 模拟赛T1【集训队作业2018】小Z的礼物

    T1: [集训队作业2018]小Z的礼物 我们发现我们要求的是覆盖所有集合里的元素的期望时间. 设\(t_{i,j}\)表示第一次覆盖第i行第j列的格子的时间,我们要求的是\(max\{ALL\}\) ...

  7. [集训队作业2018]蜀道难——TopTree+贪心+树链剖分+链分治+树形DP

    题目链接: [集训队作业2018]蜀道难 题目大意:给出一棵$n$个节点的树,要求给每个点赋一个$1\sim n$之内的权值使所有点的权值是$1\sim n$的一个排列,定义一条边的权值为两端点权值差 ...

  8. UOJ#422. 【集训队作业2018】小Z的礼物

    #422. [集训队作业2018]小Z的礼物 min-max容斥 转化为每个集合最早被染色的期望时间 如果有x个选择可以染色,那么期望时间就是((n-1)*m+(m-1)*n))/x 但是x会变,中途 ...

  9. UOJ#428. 【集训队作业2018】普通的计数题

    #428. [集训队作业2018]普通的计数题 模型转化好题 所以变成统计有标号合法的树的个数. 合法限制: 1.根标号比子树都大 2.如果儿子全是叶子,数量B中有 3.如果存在一个儿子不是叶子,数量 ...

随机推荐

  1. Nginx反向代理和Node.js后端解决跨域问题

    最近在写自己的博客,涉及到跨域的问题,自己捣鼓许久,终于解决了.然后总结一下,记录一下,日后遇到类似的问题的时候也可以得到一些启发. 一.什么是跨域 跨域,指的是浏览器不能执行其他网站的脚本.它是由浏 ...

  2. 学习day01

    1.web C/S:Client Server 客户端 服务器 QQ,... B/S:Browser Server 浏览器 服务器 PC机:Personal Computer 个人电脑 2.HTML ...

  3. async await详解

    async await本身就是promise + generator的语法糖. 本文主要讲述以下内容 async awiat 实质 async await 主要特性 async await 实质 下面 ...

  4. WEB前端需要了解的XML相关基础知识

    什么是 XML? XML 指可扩展标记语言(EXtensible Markup Language) XML 是一种标记语言,很类似 HTML XML 的设计宗旨是传输数据,而非显示数据 XML 标签没 ...

  5. Dynamics 365-关于Activity定制的一个细节

    有一个需求,是Lead上的activity创建的时候,更新regarding Entity上的某个字段信息.需求很简单,写个plugin,注册到对应activity的create事件上,Over... ...

  6. 【esri-loader】帮助文档翻译 part2 用法

    esri-loader怎么用?看完不要太清楚. [未完待续]!!! Q1: 在哪里用? 这是我最疑惑的问题之一,我知道要用esri-loader,肯定是某条js导入语句起作用的,但是你得告诉我写在哪里 ...

  7. Admin Console 反应慢的相关bug

    一个常见问题是在 Admin console 刷新 server 列表时,页面反应慢.从 Admin Server 的 Thread Dump 可以看到 Admin server 到 Managed ...

  8. 简单的shell命令

    grep echo 重定向与管道 tr 特殊文件:/dev/null,/dev/tty 基本命令查找 访问shell脚本的参数 简单的执行跟踪: set -x set +x

  9. kotlin 第一个Android项目

    一.创建过程 二.TextView点击事件 class MainActivity : AppCompatActivity() { lateinit var tv:TextView; //初始化Text ...

  10. 【Linux】【MySQL】CentOS7安装最新版MySQL8.0.13(最新版MySQL从安装到运行)

    1.前言 框框博客在线报时:2018-11-07 19:31:06 当前MySQL最新版本:8.0.13 (听说比5.7快2倍) 官方之前表示:MySQL 8.0 正式版 8.0.11 已发布,MyS ...