参考资料

百度百科

斯特林数 学习笔记-by zhouzhendong

前言

首先是因为这道题,才去研究了这个玩意:【2019雅礼集训】【第一类斯特林数】【NTT&多项式】permutation

感觉这个东西非常的...巧妙。

暴力

第一类斯特林树S(n,k)就是将n个数字划分为k个不相区分的圆排列的方案数(即忽略顺序)。

首先,第一类斯特林数有一个人尽皆知的\(O(n^2)\)递推式:

\[S(n,k)=S(n-1,k-1)+(n-1)*S(n-1,k)
\]

理解起来也是比较容易的。就是考虑新来的一个元素,可以自成一个圆排列,也可以放在前面已经有的圆排列的空位置,而一共有n-1个空位。所以说上式成立。

nlog^2n的做法

这个还是比较好理解的。

类比于二项式定理的形式,其实也有一个关于第一类斯特林数的多项式的形式:

\[x(x+1)(x+2)(x+3)...(x+n-1)=\sum_{i=0}^{n}S(n,i)x^i---(1)
\]

\[x(x-1)(x-2)(x-3)...x(x-n+1)=\sum_{i=0}^{n}(-1)^{n-i}S(n,i)x^i---(2)
\]

其中上面(1)式的左半部分称作上升幂,(2)式的左半部分称作下降幂

其实不知道推理也没关系,因为这和二项式定理一样,本身就是一个结论,规定

推导过程:

数学归纳法:

先令\(f(x,n)=x(x-1)(x-2)...(x-n+1)\)

\[=>f(x,n+1)=(x-n)f(x,n)
\]

\[=>f(x,n+1)=\sum_{i=0}^{n}S(n,i)(1-)^{n-i}x^{i+1}-n\sum_{i=0}^{n}(-1)^{n-i}S(n,i)x^i
\]

左边:令i=i-1;

右边:因为S(n,0)和S(n,n+1)都等于0,所以可以将S(n,0)替换为S(n,n+1):

\[=>f(x,n+1)=\sum_{i=1}^{n+1}S(n,i-1)(-1)^{n-i+1}x^i-\sum_{i=1}^{n+1}S(n,i-1)(-1)^{n-i}x^i
\]

再合并:

\[=>f(x,n++1)=\sum_{i=1}^{n+1}(S(n,i-1)+n*S(n,i))(-1)^{n-i+1}x^i
\]

\[=>f(x,n+1)=\sum_{i=1}^{n+1}S(n+1,i)(-1)^{n-i+1}x^i
\]

对于上升幂来说,也是如此。

为了免去(-1)这个系数的影响,下面统一使用上升幂进行讨论。

观察上述式子,我们发现,好像可以直接暴力分治FFT:假设当前是计算\((x+L)(x+L+1)...(x+R)\),然后我们考虑将L ~ mid和mid+1 ~ R分别计算后,再将两边的式子乘起来,继续往上递回溯就行了。复杂度是\(O(n\log ^2n)\)的。

nlogn的做法

然而我们还可以进一步进行优化。大致的思路是在之前的基础上,将左半部分,也就是L ~ mid的部分算出来之后,直接使用卷积计算出右半部分(mid+1 ~ R)回溯回来之后的多项式,然后将两个式子相乘之后直接回溯。如果能够实现的话,时间复杂度就降到了\(O(nlogn)\)(虽然常数很大)。

假设当前的最高项的次数为2n(先不管奇数的情况)。

现在我们具体来考虑如何通过将L ~ mid进行递归计算后返回的多项式(即\(\sum_{i=0}^{n-1}(x+i)\))的系数直接推出递归mid+1 ~ R得到的多项式(\(\sum_{i=n}^{2*n-1}(x+i)\))的系数:

左边的多项式是\(a_0+a_1x+a_2x^2+...+a_nx^n\),即\(\sum_{i=0}^{n}a_ix^i\)。

那么就可以直接得到右边一半的式子是:\(\sum_{i=0}^{n}a_i(x+n)^i\)

然后就可以得到:

\[\begin{align}
\sum_{i=1}^{n}a_i(x+n)^i &=\sum_{i=1}^{n}a_i\sum_{j=0}^{i}C_{i}^{j}x^jn^{i-j} \tag{1}\\
&=\sum_{j=0}^{n}x^{j}\sum_{i=j}^{n}C_{i}^{j}n^{i-j}a_{i} \tag{2}\\
&=\sum_{i=0}^{n}x^{i}\sum_{j=i}^{n}C_{j}^{i}n^{j-i}a_{j} \tag{3}\\
&=\sum_{i=0}^{n}x^{i}\sum_{j=i}^{n}\frac{1}{i!}*(\frac{n^{j-i}}{(j-i)!})*(j!a_j) \tag{4}
\end{align}
\]

其中(1)到(2)就是简单的二项式定理暴力展开。(2)到(3)则是交换了i和j... (3)到(4)是把\(x^i\)单独提出来了。(3)到(4)则是将inv[j!]单独提出来,并将j-i和j两个参量分开,形成了一个标准的减法卷积的形式。可以在最后的时候每一项单独乘上inv[j!]就可以了。

减法卷积怎么搞呢?

当然是选择将其中的一个多项式进行反转,最后再反转/平移回来啦(巨恶心)!然后我的做法是令\(p_i=\frac{n^{i}}{i!}\),\(q_i=i!a_{i}\),然后构造多项式\(f_1(x)=\sum_{i=0}^{n}p_ix^i\)和多项式\(f_2(x)=\sum_{i=0}^{n}q_ix^i\),然后我们将第一个多项式进行翻转,然后将两个多项式卷积起来,并设最终式(其中p,q,k的定义都是翻转前的定义)为:\(\sum_{i=0}^{n}k_ix^i\)就有:

\[\begin{align}
k_{i-j}&=\sum_{i,j} p_{j}q_i\\
k_{i+j}&=\sum_{i,j} p_{-j}q_i\\
k_{i+j-n}&=\sum_{i,j} p_{n-j}q_i\\
k'_{i+j}&=\sum_{i,j} p'_{j}q_i\\
\end{align}
\]

这样子我们就强行将这个式子转化为了一个加法卷积式。注意最后只需要将求出来左移n格就是答案了。但是感觉将翻转后的多项式进行卷积后,答案竟然需要平移回来,有点怪???

据说,将\(f_2(x)\)进行翻转的话,最后就是将卷积得到的数组再翻转回来了,有兴趣的读者可以自行尝试。

对了刚刚还没提次数为奇数的情况怎么处理。假设最高项为2n+1,那么你可以先将其看做是2n次的多项式,按照套路计算完后,再将最后一项(x+2n)暴力O(n)乘上去就可以了。

写起来总体思路并不是那么恶心,只是细节较多(写着写着就卡住了)

代码

int PowMod(int x,int y)
{
int ret=1;
while(y)
{
if(y&1)
ret=1LL*ret*x%MO;
x=1LL*x*x%MO;
y>>=1;
}
return ret;
}
void Prepare()
{
fact[0]=1;
for(int i=1;i<=MAXN;i++)
fact[i]=1LL*fact[i-1]*i%MO;
inv[MAXN]=PowMod(fact[MAXN],MO-2);//求阶乘的逆元
for(int i=MAXN-1;i>=0;i--)
inv[i]=1LL*inv[i+1]*(1LL*i+1LL)%MO;
}
void Reverse(int A[],int deg)
{
for(int i=0;i<deg/2;i++)
swap(A[i],A[deg-i-1]);
}
void NTT(int P[],int len,int oper)
{
for(int i=1,j=0;i<len-1;i++)
{
for(int s=len;j^=s>>=1,~j&s;);
if(i<j) swap(P[i],P[j]);
}
int unit,unit_p0;
for(int d=0;(1<<d)<len;d++)
{
int m=(1<<d),m2=m*2;
unit_p0=PowMod(G,(MO-1)/m2);
if(oper==-1)
unit_p0=PowMod(unit_p0,MO-2);
for(int i=0;i<len;i+=m2)
{
unit=1;
for(int j=0;j<m;j++)
{
int &P1=P[i+j+m],&P2=P[i+j];
int t=1LL*unit*P1%MO;
P1=((1LL*P2-1LL*t)%MO+MO)%MO;
P2=(1LL*P2+1LL*t)%MO;
unit=1LL*unit*unit_p0%MO;
}
}
}
if(oper==-1)
{
int inv=PowMod(len,MO-2);
for(int i=0;i<len;i++)
P[i]=1LL*P[i]*inv%MO;
}
}
void Mul(int ret[],int _x[],int l1,int _y[],int l2)//多项式乘法
{
static int RET[MAXN+5],X[MAXN+5],Y[MAXN+5];
int len=1;
while(len<l1+l2) len<<=1;
copy(_x,_x+l1,X);copy(_y,_y+l2,Y);
fill(X+l1,X+len,0);fill(Y+l2,Y+len,0);
NTT(X,len,1);NTT(Y,len,1);
for(int i=0;i<len;i++)
RET[i]=1LL*X[i]*Y[i]%MO;
NTT(RET,len,-1);
copy(RET,RET+l1+l2,ret);
}
void Get(int deg,int A[],int B[])
{
static int tmpA[MAXN+5],tmpB[MAXN+5];
int len=deg/2;
for(int i=0;i<len+1;i++)
tmpA[i]=1LL*PowMod(len,i)*inv[i]%MO;//先预处理f1(x)
fill(tmpA+len+1,tmpA+deg+1,0);
for(int i=0;i<len+1;i++)
tmpB[i]=1LL*fact[i]*A[i]%MO;//再预处理f2(x)
fill(tmpB+len+1,tmpB+deg+1,0);
Reverse(tmpA,len+1);//将f1进行翻转
Mul(tmpA,tmpA,len+1,tmpB,len+1);//相乘
for(int i=0;i<=len;i++)
tmpA[i]=1LL*tmpA[i+len]*inv[i]%MO;//最后将结果移位回来,同时乘上inv[j!]
copy(tmpA,tmpA+len+1,B);
}
void Solve(int deg,int B[])//快速求解第一类斯特林数
{//deg为最高次数项的次数(和以前的代码风格不一样,不舒服)
static int tmpB[MAXN+5];
if(deg==1)
{
B[1]=1;//终止状态为x
return;
}
Solve(deg/2,B);//先递归求左半部分
int hf=deg/2;
copy(B,B+hf+1,tmpB);//注意+1
fill(tmpB+hf+1,tmpB+deg+1,0);
Get(deg-deg%2,tmpB,tmpB+hf+1);//通过左边的返回多项式直接求右边的返回多项式
Mul(B,tmpB,hf+1,tmpB+hf+1,hf+1);//将左右两边相乘
if(deg%2==1)//奇数的情况特殊处理
for(int i=deg;i>=1;i--)
B[i]=(1LL*B[i]*(1LL*deg-1LL)%MO+1LL*B[i-1])%MO;//可以列个式子看一下
}

如何快速求解第一类斯特林数--nlog^2n + nlogn的更多相关文章

  1. 【2019雅礼集训】【CF 960G】【第一类斯特林数】【NTT&多项式】permutation

    目录 题意 输入格式 输出格式 思路 代码 题意 找有多少个长度为n的排列,使得从左往右数,有a个元素比之前的所有数字都大,从右往左数,有b个元素比之后的所有数字都大. n<=2*10^5,a, ...

  2. CF960G(第一类斯特林数)

    题目 CF960G 做法 设\(f(i,j)\)为\(i\)个数的序列,有\(j\)个前缀最大值的方案数 我们考虑每次添一个最小数,则有:\(f(i,j)=f(i-1,j)+(i-1)*f(i-1,j ...

  3. 【cf960G】G. Bandit Blues(第一类斯特林数)

    传送门 题意: 现在有一个人分别从\(1,n\)两点出发,包中有一个物品价值一开始为\(0\),每遇到一个价值比包中物品高的就交换两个物品. 现在已知这个人从左边出发交换了\(a\)次,从右边出发交换 ...

  4. CF960G Bandit Blues 第一类斯特林数、NTT、分治/倍增

    传送门 弱化版:FJOI2016 建筑师 由上面一题得到我们需要求的是\(\begin{bmatrix} N - 1 \\ A + B - 2 \end{bmatrix} \times \binom ...

  5. CF960G Bandit Blues 【第一类斯特林数 + 分治NTT】

    题目链接 CF960G 题解 同FJOI2016只不过数据范围变大了 考虑如何预处理第一类斯特林数 性质 \[x^{\overline{n}} = \sum\limits_{i = 0}^{n}\be ...

  6. CF960G Bandit Blues 分治+NTT(第一类斯特林数)

    $ \color{#0066ff}{ 题目描述 }$ 给你三个正整数 \(n\),\(a\),\(b\),定义 \(A\) 为一个排列中是前缀最大值的数的个数,定义 \(B\) 为一个排列中是后缀最大 ...

  7. 【UVA 11077】 Find the Permutations (置换+第一类斯特林数)

    Find the Permutations Sorting is one of the most used operations in real life, where Computer Scienc ...

  8. [CF960G]Bandit Blues(第一类斯特林数+分治卷积)

    Solution: ​ 先考虑前缀,设 \(f(i, j)\) 为长度为 \(i\) 的排列中满足前缀最大值为自己的数有 \(j\) 个的排列数. 假设新加一个数 \(i+1\) 那么会有: \[ f ...

  9. 【HDU 4372】 Count the Buildings (第一类斯特林数)

    Count the Buildings Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Othe ...

随机推荐

  1. H5_0004:JS设置循环debugger的方法

    在HTML页面加上如下代码,则PC打开控制台后,就会循环debugger,防止调试代码. <script>eval(function (p, a, c, k, e, r) { e = fu ...

  2. 罗技M185鼠标飘

    不用鼠标垫会好很多,艹.今天买了个树脂鼠标垫解决问题.

  3. php的api接口

    在实际工作中,使用PHP写api接口是经常做的,PHP写好接口后,前台就可以通过链接获取接口提供的数据,而返回的数据一般分为两种情况,xml和json,在这个过程中,服务器并不知道,请求的来源是什么, ...

  4. VLAN原理解释

    转发至http://network.51cto.com/art/201409/450885.htm 为什么需要VLAN 1. 什么是VLAN? VLAN(Virtual LAN),翻译成中文是“虚拟局 ...

  5. 软件测试面试必问--bug交互流程

    目前市场主要用的bug管理工具:禅道.jira.QC.bugfree等,当然也有自己公司开发的. 不过不管哪一种工具,核心交互流程都是差不多的,只是字段的名称不一样而已,参考如下两张示意图: 这是前几 ...

  6. Mac环境下Scrapy的安装

    直接命令安装: $ easy_install scrapy 从 GitHub 安装: $ git clonehttps://github.com/scrapy/scrapy.git $ cd scra ...

  7. django ajax 及批量插入数据 分页器

    ``` Ajax 前端朝后端发送请求都有哪些方式 a标签href GET请求 浏览器输入url GET请求 form表单 GET/POST请求 Ajax GET/POST请求 前端朝后端发送数据的编码 ...

  8. python之地基(二)

    上一个阶段呢,我们已经学习了python的数据的类型.今天呢,我们来学习各种各样的运算符. 一.基本运算符 a = 10    b = 20 运算符号 描述 示例 + 加——两个对象相加 a+b 输出 ...

  9. 【算法】CRF(条件随机场)

    CRF(条件随机场) 基本概念 场是什么 场就是一个联合概率分布.比如有3个变量,y1,y2,y3, 取值范围是{0,1}.联合概率分布就是{P(y2=0|y1=0,y3=0), P(y3=0|y1= ...

  10. LoadRunner脚本准备

    脚本录制1.启动LoadRunner2.打开VuGen在LoadRunner Launcher窗格中,单击创建/编辑脚本3.创建一个空白Web脚本在“新建虚拟用户”对话框里选择新建脚本的协议一般选择W ...