• [做题笔记] 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);
    }
    
  • 相关阅读:
    JS 基于面向对象的 轮播图1
    JS 原型继承的几种方法
    angularJs 自定义服务 provide 与 factory 的区别
    C# 调用JS Eval,高效率
    Linq表连接大全(INNER JOIN、LEFT OUTER JOIN、RIGHT OUTER JOIN、FULL OUTER JOIN、CROSS JOIN)
    C# LINQ干掉for循环
    C# 彻底搞懂async/await
    .NET中的异步编程——动机和单元测试
    .NET中的异步编程——常见的错误和最佳实践
    C# 实用代码段
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16428694.html
Copyright © 2020-2023  润新知