[做题笔记] Tiw 的数学选讲

[WC2021] 斐波那契

题目描述

点此看题

解法

可以把研究对象换成斐波那契数列,具体地,我们要求解这东西:

\[af_{n-1}+bf_n=0\bmod m \]

首先简化问题,可以同除 \(d=\gcd(a,b,m)\),记 \(a'=\frac{a}{d},b'=\frac{b}{d},m'=\frac{m}{d}\),那么问题变成:

\[a'f_{n-1}+b'f_n=0\bmod m' \ \ \ \ (*) \]

现在有性质:\(\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(a',\frac{m'}{\gcd(f_{n-1},m')})\gcd(f_{n-1},m')=\gcd(b',m')\gcd(f_n,\frac{m'}{\gcd(b',m')}) \]

\[\frac{\gcd(f_{n-1},m')}{\gcd(f_n,\frac{m'}{\gcd(b',m')})}=\frac{\gcd(b',m')}{\gcd(a',\frac{m'}{\gcd(f_{n-1},m')})} \]

根据上面两条互质的性质,等式两边都是最简分数,所以可以得到 \(\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{-a'}{b'/g}=\frac{f_n}{f_{n-1}/g}\bmod \frac{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_{i,j}=(1+j+{j\choose 2})\cdot f_{i-1,j}+\sum_{k=0}^{j-1} {j+2\choose k}\cdot f_{i-1,k} \]

复杂度瓶颈在于后半部分,可以把它变一下形:

\[(j+2)!\sum_{k=0}^{j-1}\frac{f_{i-1,k}}{k!}\cdot\frac{1}{((j-1)-k+3)!} \]

这样就可以直接卷积了,具体来说把 \(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);
}
posted @ 2022-06-30 21:31  C202044zxy  阅读(225)  评论(0编辑  收藏  举报