[做题笔记] Tiw 的数学选讲
[WC2021] 斐波那契
题目描述
解法
可以把研究对象换成斐波那契数列,具体地,我们要求解这东西:
首先简化问题,可以同除 \(d=\gcd(a,b,m)\),记 \(a'=\frac{a}{d},b'=\frac{b}{d},m'=\frac{m}{d}\),那么问题变成:
现在有性质:\(\gcd(\gcd(a',m'),\gcd(b',m'))=1\);此外有斐波那契数列的性质:\(\gcd(f_n,f_{n-1})=1\)
由于与 \(m'\) 取模不影响与 \(m'\) 的 \(\gcd\),所以有 \(\gcd(a'f_{n-1},m')=\gcd(b'f_n,m')\),可以把 \(\gcd\) 的式子拆分,对于等式左边的部分,可以先和 \(f_{n-1}\) 取公因数,再和 \(a\) 取公因数,等式右边也同理,那么可以得到:
根据上面两条互质的性质,等式两边都是最简分数,所以可以得到 \(\gcd(f_{n-1},m')=\gcd(b',m')\),这个性质给了我们沟通 \(f_{n-1}/f_n\) 与 \(a'/b'\) 之间的桥梁。
设 \(g=\gcd(f_{n-1},m')=\gcd(b',m')\),回看 \((*)\) 式子,可以同除 \(g\),那么化简成:
由于在模 \(\frac{m'}{g}\) 意义下,\(\frac{b'}{g}\) 和 \(\frac{f_{n-1}}{g}\) 都存在逆元,所以对于一个询问,我们可以很轻易地计算出左半边。对于右半边可以直接对于所有的 \(m'\) 预处理( \(m'\) 是 \(m\) 的因数),因为 \(g=\gcd(f_{n-1},m')\) 可以把所有的取值放在 map
中。
时间复杂度 \(O(\sum m'\cdot \log m)=O(m\log^2 m)\)
#include <cstdio>
#include <cassert>
#include <iostream>
#include <unordered_map>
using namespace std;
const int M = 100005;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m;
unordered_map<int,unordered_map<int,int> >mp[M],h;
int gcd(int a,int b) {return !b?a:gcd(b,a%b);}
int exgcd(int a,int b,int &x,int &y)
{
if(b==0) {x=1;y=0;return a;}
int d=exgcd(b,a%b,y,x);
y-=(a/b)*x;return d;
}
int inv(int a,int p)
{
int x=0,y=0,d=exgcd(a,p,x,y);
assert(d==1);
return (x%p+p)%p;
}
signed main()
{
n=read();m=read();mp[1][1][0]=2;
for(int x=2;x<=m;x++) if(m%x==0)
{
int a=1,b=1;h.clear();
for(int i=2;;i++,swap(a,b),(b+=a)%=x)
{
if(h[a].count(b)) break;h[a][b]=1;
int d=gcd(a,x),c=b*inv(a/d,x/d)%(x/d);
if(!mp[x][d].count(c)) mp[x][d][c]=i;
}
}
while(n--)
{
int a=read(),b=read();
if(!a) {puts("0");continue;}
if(!b) {puts("1");continue;}
int d=gcd(gcd(a,b),m),k=m/d;
a/=d;b/=d;d=gcd(b,k);
int c=(k-a)*inv(b/d,k/d)%(k/d);
if(mp[k][d].count(c))
printf("%lld\n",mp[k][d][c]);
else puts("-1");
}
}
[AGC021F] Trinity
题目描述
解法
可以从 \(A\) 入手,感觉我们确定了每一行的第一个黑格是谁,再对不同的 \((B,C)\) 计数就会比较容易。
考虑按列 \(dp\),需要完成的任务是:一些行填上第一个黑格,在此基础上对这一列的 \((B_i,C_i)\) 计数。设 \(f[i][j]\) 表示考虑了前 \(i\) 列,有 \(j\) 行的第一个黑格已经确定的方案数。
如果 \(\Delta j=0\),那么 \((B_i,C_i)\) 只能根据原来的 \(j\) 行生成(这里的生成是指这 \(j\) 行可以任意选择在这一列放不放黑格,这会导致不同的 \((B_i,C_i)\) 组合),有空行、单格、双格三种情况,方案数是 \(1+j+{j\choose 2}\)
如果 \(\Delta j>0\),根据 \(B_i,C_i\) 的生成情况讨论,注意这 \(\Delta j\) 行必须选择放置黑格:
- 若都由 \(\Delta j\) 生成,那么只需要选出这 \(\Delta j\) 行,方案数是 \({j+\Delta j\choose j}\)
- 若一个由 \(j\) 生成,一个由 \(\Delta j\) 生成,可以两步一起计数(边界的一个就是 \(j\)),方案数是 \(2{j+\Delta j\choose j+1}\)
- 若都由 \(j\) 生成,也可以两步一起计数(边界的两个就是 \(j\)),方案数是 \({j+\Delta j\choose j+2}\)
- 把上面三者加起来,就可以得到总方案数 \({j+\Delta j+2\choose j}\)
根据上面的分析,可以写出这样的转移方程:
复杂度瓶颈在于后半部分,可以把它变一下形:
这样就可以直接卷积了,具体来说把 \(F(x)=\sum_{j=0}^n\frac{f_{i-1,j}}{j!}\cdot x^j\) 和 \(G(x)=\sum_{j=0}^{n} \frac{1}{(j+3)!}\cdot x^j\) 卷起来即可。最后的答案是:\(\sum_{i=0}^n {n\choose i}\cdot f_{m,i}\),时间复杂度 \(O(nm\log n)\)
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 100005;
#define int long long
const int MOD = 998244353;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,w,fac[M],inv[M],f[2][M],rev[M],A[M],B[M],C[M];
void init(int n)
{
fac[0]=inv[0]=inv[1]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
for(int i=2;i<=n;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=2;i<=n;i++) inv[i]=inv[i-1]*inv[i]%MOD;
}
int comb(int n,int m)
{
if(n<m || m<0) return 0;
return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
int qkpow(int a,int b)
{
int r=1;
while(b>0)
{
if(b&1) r=r*a%MOD;
a=a*a%MOD;
b>>=1;
}
return r;
}
void NTT(int *a,int len,int op)
{
for(int i=0;i<len;i++)
{
rev[i]=(rev[i>>1]>>1)|((len/2)*(i&1));
if(i<rev[i]) swap(a[i],a[rev[i]]);
}
for(int s=2;s<=len;s<<=1)
{
int t=s/2,w=(op==1)?qkpow(3,(MOD-1)/s):
qkpow(3,MOD-1-(MOD-1)/s);
for(int i=0;i<len;i+=s)
for(int j=0,x=1;j<t;j++,x=x*w%MOD)
{
int fe=a[i+j],fo=a[i+j+t];
a[i+j]=(fe+x*fo)%MOD;
a[i+j+t]=(fe-x*fo%MOD+MOD)%MOD;
}
}
if(op==1) return ;
int inv=qkpow(len,MOD-2);
for(int i=0;i<len;i++) a[i]=a[i]*inv%MOD;
}
signed main()
{
n=read();m=read();init(1e5);
f[0][0]=f[1][0]=1;
while(m--)
{
int len=1;while(len<2*n) len<<=1;
for(int i=0;i<len;i++) A[i]=B[i]=C[i]=0;
w^=1;
for(int i=0;i<n;i++)
A[i]=f[w^1][i]*inv[i]%MOD,B[i]=inv[i+3];
NTT(A,len,1);NTT(B,len,1);
for(int i=0;i<len;i++) C[i]=A[i]*B[i]%MOD;
NTT(C,len,-1);
for(int i=1;i<=n;i++)
f[w][i]=((i*i+i+2)/2*f[w^1][i]+fac[i+2]*C[i-1])%MOD;
}
int ans=0;
for(int i=0;i<=n;i++)
ans=(ans+comb(n,i)*f[w][i])%MOD;
printf("%lld\n",ans);
}