Tiw_Air_OAO 数学杂题选讲
伯努利数求自然数幂前缀和
(指数型生成函数)形式更为常用,不如说 (普通型生成函数)几乎没用。
例:
后面的分式可以拆成 (只有常数项不为零的形式幂级数才可以求逆)与 的乘积。
前者与 无关,可以预先求出。事实上,这就是伯努利数的 。
与斯特林数的异同
斯特林数也可以用于求自然数幂和。
但其实它们是差不多的!回忆斯特林数的 :
那么:
这其中关键的一步就是 的指标代换,这事实上也是斯特林反演的关键。它成立的原因是: 常数项是零,从而当 时 。
两个做法在第二步出现分歧,伯努利数直接对原式化简,而斯特林反演则是将关于 的函数代换成 。
第二个做法可以避免求逆元。
[NOI Online 2021 提高组] 愤怒的小 N
设集合 对应 ,那么 就是 中所有数的 次幂之和。
我们想要求出长度为 的前缀对应的集合 。由于字符串的特殊性,可以考虑倍增。
分别维护长度为 的前缀 对应的集合的生成函数 ,有如下转移式成立:
仔细瞪一下,作换元 与 能够给出更简洁的递推式:
发现 ,就是自然数幂和,同时 常数项为 ,因此 ,我们只需要求前 项。拿 凑 的 ,得到:
事实上指标 的有效范围只到 ,直接 算出来。另一方面 的自然数幂和可以拉格朗日插值,最终复杂度是 。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,k;
char s[500005];
const int md=1e9+7;
inline int pwr(int x,int y){
int res=1;
while(y){
if(y&1)res=1ll*res*x%md;
x=1ll*x*x%md;y>>=1;
}
return res;
}
int a[505],fac[505],ifac[505],inv[505],Pwr[500005];
int T[505][505],e[505],t[505],tmp[505];
inline void mul(int *a,int *b,int *c){
for(int i=0;i<k;i++)c[i]=0;
for(int i=0;i<k;i++){
for(int j=0;i+j<k;j++)c[i+j]=(c[i+j]+1ll*a[i]*b[j])%md;
}
}
int pre[505],suf[505];
inline int lagrange(int *y,int x){
int res=0;pre[0]=x;suf[k+1]=1;
for(int i=1;i<=k;i++)pre[i]=1ll*pre[i-1]*(x-i)%md;
for(int i=k;i>=1;i--)suf[i]=1ll*suf[i+1]*(x-i)%md;
for(int i=0;i<=k;i++){
int tmp=1ll*ifac[i]*ifac[k-i]%md*((k-i)&1?md-1:1)%md;
if(i)tmp=1ll*tmp*pre[i-1]%md;
if(i!=k)tmp=1ll*tmp*suf[i+1]%md;
res=(res+1ll*y[i]*tmp)%md;
}
return res;
}
inline void init(){
Pwr[0]=fac[0]=fac[1]=inv[0]=inv[1]=ifac[0]=ifac[1]=1;
for(int i=2;i<=k;i++)fac[i]=1ll*fac[i-1]*i%md;
for(int i=2;i<=k;i++)inv[i]=1ll*(md-md/i)*inv[md%i]%md;
for(int i=2;i<=k;i++)ifac[i]=1ll*ifac[i-1]*inv[i]%md;
for(int i=1;i<=n;i++)Pwr[i]=2ll*Pwr[i-1]%md;
T[0][0]=1;
for(int i=1;i<k;i++){
for(int j=1;j<k;j++)e[j]=1ll*ifac[j]*pwr(Pwr[i-1],j)%md;
for(int j=1;j<k;j++)e[j]=(md-e[j])%md;mul(T[i-1],e,T[i]);
// cout<<i<<endl;
// for(int j=0;j<k;j++)cout<<T[i][j]<<" ";cout<<endl;
}
int sum=0,op=-1;
for(int i=n-1;i>=k;i--){
if(s[i])sum=(sum+Pwr[i])%md,op*=-1;
}
for(int i=k-1;~i;i--){
if(s[i]){
for(int j=0;j<k;j++)e[j]=1ll*ifac[j]*pwr(sum,j)%md;
mul(T[i],e,tmp);
for(int j=0;j<k;j++)t[j]=(t[j]+1ll*op*tmp[j]+md)%md;
sum=(sum+Pwr[i])%md;op*=-1;
}
}
for(int i=0;i<k;i++)t[i]=1ll*t[i]*fac[i]%md;
// for(int i=0;i<k;i++)cout<<t[i]<<" ";cout<<endl;
for(int i=0;i<k;i++)scanf("%d",&a[i]);
for(int i=0;i<k;i++){
tmp[0]=(i==0);
for(int j=1;j<=k;j++)tmp[j]=(tmp[j-1]+pwr(j,i))%md;
t[i]=1ll*(t[i]+lagrange(tmp,sum-1))%md*pwr(2,md-2)%md;
}
// for(int i=0;i<k;i++)cout<<t[i]<<" ";cout<<endl;
int res=0;
for(int i=0;i<k;i++)res=(res+1ll*a[i]*t[i])%md;
printf("%d\n",res);
}
int main(){
scanf("%s",s);n=strlen(s);
reverse(s,s+n);for(int i=0;i<n;i++)s[i]-='0';
scanf("%d",&k);init();
return 0;
}
hdu6593 Coefficient
可以换元 ,因此我们就假定 吧。
根据 ,可以想到接下来的形式必然是 。系数的变化规律是:
是统一的,可忽略之,最后答案乘 。初值 。最后的答案就是 。
考虑系数的 “移动”(因为是线性变换),可见路径权值是 ,其中 且 ,贡献到 。这就可以用 轻易描述了:
我不熟悉它,所以我没能指出: 是 第二类斯特林数 在第 列上的 。其实想想 不就是每次要么放进新盒子、要么放进已有的盒子,第二类斯特林数吗!
“你看到的只是表象。” —— 太阳神
因此 即得。用斯特林数在第 行上的 去算即可。
再或者,我们可以换一种刺杀的方式。直接从生成函数的角度考虑它,答案就是
吸取 “” 的经验,只需把 展开成
我们先算出
Remark:把 去掉后,其实 就是第二类斯特林数在第 列上的 。因此我们得到的也就是第二类斯特林数在第 行上的 。
直接得到
系数 是简单卷积,然后对 个 多点求值。复杂度 。
点击查看代码
#4126. 国王奇遇记加强版之再加强版
求:
用 把 放到指数外,有:
其中分母是常多项式,可以分析到答案是关于 的多项式。令 ,则:
可见,若令:
则:
但是我们对 ,甚至对 一无所知。怎么办?其实 较小的时候,我们可以用答案最初的式子算出来,继而得到若干 与 的值的线性关系。再者, 是 次多项式,其 阶差分为 。所以:
利用已知线性关系解出 ,得到点值, 差值。线性筛自然数幂、线性求一堆逆元,精细实现可以做到 。
#6718. 九个太阳「弱」化版
给定 ,满足 是 的幂,求
对 取模。
发现:
进而 就是 中 的倍数次项,即 的常数项,对 做长度为 的循环卷积即可。
「Karatsuba 算法」
设要计算 次多项式 和 的卷积,令 ,,则:
需要三次卷积,每次卷积递归计算。复杂度 。
不过还是过不了这题,还是要写 或 的 并每次取模。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int md=998244853,wei=15;
int R[1000005];
int pw(int x,int y,int md){
int ans=1;
while(y){
if(y&1)
ans=1ll*ans*x%md;
x=1ll*x*x%md;
y>>=1;
}
return ans;
}
const long double pi=acos(-1.0);
struct CP{
long double a,b;
CP(){}
CP(long double c,long double d){
a=c;b=d;
}
friend CP operator + (const CP &x,const CP &y){
return CP(x.a+y.a,x.b+y.b);
}
friend CP operator - (const CP &x,const CP &y){
return CP(x.a-y.a,x.b-y.b);
}
friend CP operator * (const CP &x,const CP &y){
return CP(x.a*y.a-x.b*y.b,x.a*y.b+x.b*y.a);
}
friend CP operator ~ (const CP &x){
return CP(x.a,-x.b);
}
};
void fft(CP a[],int len,int type){
for(int i=0;i<len;i++)
if(i<R[i])swap(a[i],a[R[i]]);
for(int mid=1;mid<len;mid*=2){
CP wn=CP(cos(pi/mid),type*sin(pi/mid));
for(int j=0;j<len;j+=mid*2){
CP w=CP(1,0);
for(int k=0;k<mid;k++,w=w*wn){
CP x=a[j+k],y=w*a[j+k+mid];
a[j+k]=x+y;a[j+k+mid]=x-y;
}
}
}
if(type==1)return;
for(int i=0;i<len;i++){
a[i].a/=len;a[i].b/=len;
}
}
CP A[1000005],B[1000005],C[1000005],D[1000005];
inline void mul(int a[],int b[],int n){
int len=n;
for(int i=0;i<len;i++){
R[i]=(R[i>>1]>>1)|((i&1)*(len/2));
A[i]=CP(a[i]>>wei,a[i]&((1<<wei)-1));
B[i]=CP(b[i]>>wei,b[i]&((1<<wei)-1));
}
fft(A,len,1);fft(B,len,1);
for(int i=0;i<len;i++){
int j=((len-i)&(len-1));
CP a0=(~A[j]+A[i])*CP(0.5,0),a1=(~A[j]-A[i])*CP(0,0.5),b0=(~B[j]+B[i])*CP(0.5,0),b1=(~B[j]-B[i])*CP(0,0.5);
C[i]=a0*b0+a1*b1*CP(0.0,1.0);D[i]=a0*b1+a1*b0;
}
fft(C,len,-1);fft(D,len,-1);
for(int i=0;i<n;i++){
long long tmp1=(long long)(C[i].a+0.5)%md,tmp2=(long long)(D[i].a+0.5)%md,tmp3=(long long)(C[i].b+0.5)%md;
a[i]=((tmp1<<(wei*2))+(tmp2<<wei)+tmp3)%md;
}
}
int ans[1000005];
void solve(long long n,int k){
if(n==1){
ans[0]++;ans[1%k]++;
return;
}
solve(n/2,k);
mul(ans,ans,k);
if(n&1){
int tmp=ans[k-1];
for(int i=k-1;i>=1;i--)ans[i]=(ans[i]+ans[i-1])%md;
ans[0]=(ans[0]+tmp)%md;
}
}
int main(){
long long n;
int k;
scanf("%lld%d",&n,&k);
solve(n,k);
printf("%d",ans[0]);
return 0;
}
hdu6355 fireflies
给定 ,以及 个点 , 。
一只萤火虫可以从 开始起飞,每次飞翔使得某个坐标增加 ,并点亮路过的点,直到飞到终点 。
请求出最少需要多少只萤火虫才能点亮所有点。, 。
转最大反链。以下为方便起见,记 。
上述问题可以形式化成:给定包含 种元素的多重集 ,其中 种元素出现 次。定义子集之间的偏序为 ,求最大反链。
定理: 记 ,则选择所有大小为 的子集即达到最大反链。
我们称子集链 是对称链,当且仅当 且 。注意 可能为 ,此时 。
引理: 存在对 进行 “对称链分解” 的方式,使得 的每个子集在恰好一条链中出现。
证明: 归纳构造。当 时 是一条链。然后每次加入 个元素 ,对于原本的对称链 ,可将其变为 条对称链。
可以画图来直观感受它。原本的链就是 ,是 轴上的点,而现在 成为 轴可选值。这个划分方式就是,先从 开始,进行 “无脑搜索”——往上一直走,直到下一个点已走过,然后往右一直走。再从 分别进行 “无脑搜索”。
不难验证其仍为对称链。
注意到,大小为 的子集在每个对称链里都出现了恰好一次。因此,这样的集合的数量就是最长反链的长度(我们构造出了其对应的链覆盖)。
剩下的部分就是折半、容斥,求 的整数解的个数。重点其实在于 。
点击查看代码
ioi2021 集训队互测 子集匹配
设 ,记 。若 满足 则二者之间连边。请构造大小为 的匹配。保证 。
用对称链构造即可。
[WC2021] 斐波那契
定义 ,,()。
现在给定 组询问,对于每组询问请找到一个最小的整数 ,使得 。
。
一个初等的思考方法是:转移矩阵的幂可以用斐波那契数填出。
如果互质性比较好,逆元存在,那么直接分离开来变成 。我们继续这个思路(分离 与 ):
考虑移项,并给 同时除掉 ,得到:
此时 ,但 仍可以 。
由于 ,此时应有:
(符号问题请大家自觉忽略! 换个元 就解决了! )
此时等式两边以及模数同除 即可实现互质,于是就分离开来了!
那么在 处塞个三元组 ,之后直接查询即可。
斐波那契数的循环节是 的。那么该算法预处理的复杂度为 ,其 中 是因子和,查询的复杂度是 。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
void exgcd(int a,int b,int &x,int &y){
if(!b){x=1;y=0;return ;}
exgcd(b,a%b,y,x);
y-=a/b*x;
}
inline int Inv(int x,int md){
int a,b;exgcd(x,md,a,b);
return (a%md+md)%md;
}
struct node{
int x,y,z;
node(int _x,int _y,int _z){
x=_x;y=_y;z=_z;
}
inline bool operator <(const node &b)const{
if(x!=b.x)return x<b.x;
if(y!=b.y)return y<b.y;
return z<b.z;
}
};
map<node,int> mp[100005];
int gcd(int x,int y){
return y?gcd(y,x%y):x;
}
inline void init(){
for(int i=2;i<=m;i++){
if(m%i==0){
int x=1,y=0;
for(int j=0;;j++){
if(x&&y){
int p=gcd(x,i),q=gcd(y,i),m1=i/p/q;
int k=1ll*(y/q)*Inv(x/p,m1)%m1;
if(!mp[i].count(node(k,p,q)))mp[i][node(k,p,q)]=j;
}
swap(x,y);y=(x+y)%i;
if(x==1&&y==0)break;
}
}
}
}
int main(){
scanf("%d%d",&n,&m);init();
for(int i=1;i<=n;i++){
int a,b;scanf("%d%d",&a,&b);
b=(m-b)%m;
if(a==0)puts("0");
else if(b==0)puts("1");
else {
int d=gcd(gcd(a,b),m),m1=m/d;a/=d,b/=d;
int p=gcd(a,m1),q=gcd(b,m1),m2=m1/p/q;a/=p,b/=q;
int k=1ll*a*Inv(b,m2)%m2;
if(mp[m1].count(node(k,q,p)))printf("%d\n",mp[m1][node(k,q,p)]);
else puts("-1");
}
}
return 0;
}
[AGC021F] Trinity
现有一个 行 列的、仅包含黑白格的表格,左上为 ,右下为 。 对于一个表格,设长度为 的数列 ,长度为 的数列 、 分别表示:
- 表示第 行第一个黑格的列号。若不存在则为 。
- 表示第 列第一个黑格的行号。若不存在则为 。
- 表示第 列最后一个黑格的行号。若不存在则为 。
现请你求出所有的 种表格中,不同的数列三元组 的个数对 取模的结果。
我们跳过 推理,得到 转移式如下
最后答案为 。
自然考虑写成 : 记 。
那么转移式可以改写成如下的微分方程:
之后可以考虑将 写成 的形式,这样转移的代价就降到了 。
这样的时间复杂度其实是 与 无关。
点击查看代码
#include <bits/stdc++.h>
#define mod 998244353
using namespace std;
inline int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9')f=(c=='-')?-1:f,c=getchar();
while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
return x*f;
}
inline int M(int x){return x>=mod?x-mod:x;}
int fsp(int bs,int p){
int rt=1;
while(p){
if(p&1)rt=1ll*rt*bs%mod;
bs=1ll*bs*bs%mod,p>>=1;
}
return rt;
}
inline int C2(int x){return x*(x-1)/2;}
int fac[10004],caf[10004];
inline int C(int x,int y){
return 1ll*fac[x]*caf[y]%mod*caf[x-y]%mod;
}
inline void init(){
int lim=10000;
fac[0]=1;
for(int i=1;i<=lim;i++)fac[i]=1ll*fac[i-1]*i%mod;
caf[lim]=fsp(fac[lim],mod-2);
for(int i=lim;i>=1;i--)caf[i-1]=1ll*caf[i]*i%mod;
}
int rtt[20004],O[20004],ny;
inline void getrtt(int w,int len){
for(int i=1;i<len;i++)rtt[i]=rtt[i>>1]>>1|((i&1)<<w);
for(int l=2;l<=len;l<<=1)O[l]=fsp(3,(mod-1)/l);
ny=fsp(len,mod-2);
}
struct Poly{
int x[20004];
int &operator[](int p){return x[p];}
inline void ntt(int len){
for(int i=1;i<len;i++)if(rtt[i]>i)swap(x[rtt[i]],x[i]);
for(int l=2;l<=len;l<<=1){
for(int i=0,m=l>>1;i<len;i+=l){
for(int j=i,tO=1,t;j<i+m;j++){
t=1ll*tO*x[j+m]%mod,tO=1ll*tO*O[l]%mod;
x[j+m]=M(x[j]-t+mod),x[j]=M(x[j]+t);
}
}
}
}
inline void idft(int len){
ntt(len),reverse(x+1,x+len);
for(int i=0;i<len;i++)x[i]=1ll*x[i]*ny%mod;
}
}f,g,tf;
int n,m;
int main(){
n=read();m=read();
init();
int w=1,len=2;
while(len<2*n+1)len<<=1,++w;
for(int i=1;i<=n;i++)g[i]=caf[i+2];
getrtt(w-1,len),g.ntt(len);
tf[0]=f[0]=1;
for(int i=1;i<=m;i++){
f.ntt(len);
for(int j=0;j<len;j++)f[j]=1ll*f[j]*g[j]%mod;
f.idft(len);
for(int j=0;j<=n;j++){
tf[j]=M(1ll*f[j]*fac[j+2]%mod+1ll*tf[j]*(1+j+C2(j))%mod);
f[j]=1ll*tf[j]*caf[j]%mod;
}
for(int j=n+1;j<len;j++)f[j]=0;
}
int res=0;
for(int i=0;i<=n;i++)res=M(res+1ll*C(n,i)*tf[i]%mod);
printf("%d\n",res);
return 0;
}
[2019 江苏省队集训] Day1 T1 光影交错
两个人玩游戏,有 的概率 获胜,有 的概率 获胜,还有 的概率两人平局。, 。
当然两人可以一直玩下去,于是每局游戏后 的概率两人停止游戏, 。
问游戏停止时,有多少时刻 的胜利局数严格多余 的胜利局数。
原题容许较大的精度误差,但是我们可以给出其封闭形式!
首先特判 ,以下假设 ,另还有 。
记 ,则答案为:
作换元 :
注意 ,因此 在收敛域内,再作换元 :
之前我的推法是直接处理 ,但总感觉不是很自然。
现在想想,可以尝试作换元 使得两元独立:
记 ,则 ,可以通过拉格朗日反演推得,收敛域为 。
由于 ,由均值不等式可得 ,又有 。
但是这个形式并无法兼容 的情况,我的解决方案是,先假设 ,最后再代入 来验证形式的统一。所以:
然后一通分析 收敛域 内 (由于条件 成立) :
我们至少得到了一个封闭形式。但很遗恫的是,这个封闭形式对某些边界值末定义。尝试 继续化简:
最后这个形式就比较良好了。当然,也可以把换过的元给代回去,得到完全用题目中的变量表达的式子:
点击查看代码
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义