返回顶部

山东五一集训

前言

不知不觉已经加入TSOI八个月,之前还遥不可及的数学集训这么快就到了,再加上已经很久没出远门。
所以

TSOI2024进军山东!



TSOI2024和wsb及小魏的合照。

2025.04.30

大约在晚上八点多到达酒店。
为什么这么晚呢,因为小魏没抢到时间好的火车票(wcy学长说坐火车没有生活,我表示认同,谴责小魏
到酒店之后一起在小魏的房间吃了个饭

味道还是不错的。
之后就是颓废时间原神真好玩

正文

2025.05.01上午

差不多5:30就起床了(唐一的生物钟真强大),刚起床就看到jmx在看手机,我也不甘示弱,继续颓废。

组合数学

来看一些简单的东西

加法原理&乘法原理

加法原理:如果做⼀件事有两类⽅式,第⼀类有\(a\)种⽅法,第⼆类有\(b\)种⽅法,那么完成这件事共有\(a+b\)种⽅法。
乘法原理:如果做⼀件事有两个部分,第⼀部分有\(a\)种⽅法,第⼆部分有\(b\)种⽅法,那么完成这件事共有\(a*b\)种⽅法。
很简单对吧。

排序

现有一个包含\(n\)个人的队伍,问你有几种排列方法。
根据乘法原理答案显然是

\[n!=n*(n-1)*(n-2)……*1 \]

提高一下难度
现有\(n\)个人,从其中选出\(m\)个人,有多少种选法。
这也不难,答案就是

\[\frac{n!}{m!(n-m)!} \]

我们将这个问题的答案记为

\[\binom{n}{m}=\frac{n!}{m!(n-m)!} \]

继续这个问题,现有\(n\)个人,从其中选出\(m\)个人,有多少种排队方法。
这个问题的答案就是上一个问题答案乘以\(m!\)
此问题的答案记作

\[A_n^m=\frac{n!}{(n-m)!} \]

继续下一个问题。
有一个多重集,里边含有\(n_{1}\)\(1\)\(n_2\)\(2\),……,\(n_m\)\(m\)
那么,这个多重集有多少种排列方法呢?
让我们假设\(n=n_1+n_2……+n_m\)
那么答案就是

\[\frac{n!}{\prod\limits_{i=0}^m n_i!} \]

再来一道题
求方程
\(x_1+x_2+x_3+……+x_m=n\)
的正整数解的个数
看起来好像有点难度,那么我们不妨将题目转换一下
\(n\)个小球排成一行,选取\(m-1\)个空隙,插上隔板,有几种插法?
显然,这两个题目是等价的,那么答案遍呼之欲出

\[\binom{n-1}{m-1} \]

二项式定理

\[(x+y)^n=\sum_{i=0}^n \binom{n}{i}x^i y^{n-i} \]

让我们尝试证明一下
首先,将这个式子展开
\((x+y)*(x+y)*……*(x+y)\)
如果我们在每个式子中选取\(i\)\(x\)
很显然有\(\binom{n}{i}\)种选择方法,
而剩下的\(n-i\)个式子则组成了\(y^{n-i}\)
所以在打开后表现为\(x^i y^{n-i}\)形式的共有\(\binom{n}{i}\)
\(i\)\(1\)取到\(n\),答案就出来了。

组合数的性质

在了解二次项定理后,我们可以推出一些关于组合数十分有用的性质
性质一:

\[\binom{n}{m}=\binom{n-1}{m-1}+\binom{n-1}{m} \]

这个很简单,直接带公式就好了。
观察这个式子。
有没有一点发现?
没错,这就是杨辉三角。
\(1\)
\(1\) \(1\)
\(1\) \(2\) \(1\)
\(1\) \(3\) \(3\) \(1\)
\(1\) \(4\) \(6\) \(4\) \(1\)
\(1\) \(5\) \(10\) \(10\) \(5\) \(1\)
性质二:

\[\binom{n}{m}=\frac{n-m+1}{m} \binom{n}{m-1} \]

\[\binom{n}{m}=\binom{n}{n-m} \]

同上,推公式可得。
性质三:

\[\sum^n_{i=1}\binom{i}{m}=\binom{n+1}{m+1} \]

好像有些复杂,别慌,让我们拆解一下
根据性质一

\[\begin{align*} \binom{n+1}{m+1}&=\binom{n}{m}+\binom{n}{m+1}\\ &=\binom{n}{m}+\binom{n-1}{m}+\binom{n-1}{m+1}\\ &=\binom{n}{m}+\binom{n-1}{m}+\binom{n-2}{m}+\binom{n-2}{m+1} \end{align*} \]

有没有看出一些端倪?
我们一直拆下去。
因为当\(n<m\)我们规定

\[\binom{n}{m}=0 \]

所以在拆解\(n\)次后最后终将变为\(0\),这个性质也就被推出来了。
性质四:

\[\sum^n_{i=0}\binom{n}{i}=2^n \]

根据二项式定理

\[(x+y)^n=\sum_{i=0}^n \binom{n}{i}x^i y^{n-i} \]

所以

\[\begin{align*} 2^n&=(1+1)^n\\ &=\sum_{i=0}^n \binom{n}{i}1^i 1^{n-i}\\ &=\sum_{i=0}^n \binom{n}{i} \end{align*} \]

性质五:

\[\sum^k_{i=0}\binom{n}{i}\binom{m}{k-i}=\binom{n+m}{k} \]

很明显

\[(x+y)^n*(x+y)^m=(x+y)^{n+m} \]

那么左边的式子\(x^k\)的系数就是

\[\sum_{i=0}^k\binom{n}{i}\binom{m}{k-i} \]

就是从\((x+y)^n\)里面选择\(i\)\(x\),然后从\((x+y)^m\)里面选择\(k-i\)\(x\)
对于右边的式子\(x^k\)的系数是

\[\binom{n+m}{k} \]

由于左右的式子相等,所以

\[\sum_{i=0}^k\binom{n}{i}\binom{m}{k-i}=\binom{n+m}{k} \]

卡特兰数

假设\(C_n\)是具有\(n\)个节点的不同的有根二叉树的个数。
两个二叉树不同,当且仅当以下三者之一成立。

  • 一个为空,一个非空。
  • 根的左子树不同。
  • 根的右子树不同

那么怎么计算\(C_n\)呢,
我们很快就可以想出一个递推式。

\[C_n=\sum^{n-1}_{i=0}C_i*C_{n-i-1} \]

直接枚举左右子树即可。
但是上面的式子是\(n^2\)的。
事实上,有一个更简单的公式。

\[\begin{align*} C_n&=\binom{2n}{n}-\binom{2n}{n-1}\\ &=\frac{\binom{2n}{n}}{n+1} \end{align*} \]

怎么推呢?
我也不会(没学呢,别喷)。

2025.05.01下午

数论

1.一些定义
如果\(a\)\(b\)整除,或者\(b\)整除\(a\),记作\(a\mid b\)
一些显然的性质:
(1)\(d\mid a\)并且\(d\mid b\)那么\(d\mid (a\pm b)\)
(2)\(b\mid a\)那么\(bc\mid ac\)
(3)\(b\mid a\),\(d\mid a\),\(d\mid b\)那么\(\frac{b}{d}\mid\frac{a}{d}\)
2.gcd&lcm
欧几里得算法:\(gcd(a,b)=gcd(a-b,b)=gcd(a+b,b)=gcd(a \bmod b,b)\)
又名辗转相除法,原理就是一直模下去,直到最后其中一个数变为\(0\),那就说明除开了,另一个数就是最大公约数。
来一个练习题:

\[gcd(a^n-1,a^m-1)=?(a>0) \]

开整

\[\begin{align*} gcd(a^n-1,a^m-1)&=gcd(a^n-a^m,a^m-1)\\ &=gcd(a^m(a^{n-m}-1),a^m-1)\\ &=gcd(a^{n-m}-1,a^m-1)\\ &=gcd(a^{n\bmod m}-1,a^m-1)\\ &=a^{gcd(n,m)}-1 \end{align*} \]

有点不懂,来看解释。
第一步:运用\(gcd(a,b)=gcd(a-b,b)\),左边减去\(a^m\)(这里假设\(n>m\))。
第二步:提出\(a^m\)
第三步:因为相邻的两个数一定互质,所以去掉\(a^m\).
重复以上三步,让\(n\)一直减\(m\),直到小于\(m\),也就是等于\(n\) \(mod\) \(m\)
一直进行辗转相除,到最后指数为\(gcd(n,m)\)

扩展欧几里得算法(exgcd)

给定整数\(a\),\(b\),\(c\),求一组整数使

\[xa+yb=c \]

怎么证明这个方程有解呢?
因为\(x\),\(y\)均为整数,所以不论如何,\(c\)\(gcd(a,b)\)的倍数时才有解。
那么我们不妨设\(d=gcd(a,b)\),那么我们只需要求出\(ax+by=d\)的解然后乘上 \(\frac{c}{d}\) 即可。
因为

\[gcd(a,b)=gcd(a,a \bmod b)=d \]

所以我们设\(x^{'}b+y^{'}(a\bmod b)=d\)
那么

\[ax+by=x^{'}b+y^{'}(a\bmod b) \]

\[ax+by=x^{'}b+y^{'}(a-b*\lfloor\frac{a}{b}\rfloor) \]

\[ax+by=ay^{'}+b(x^{'}-y^{'}\lfloor\frac{a}{b}\rfloor) \]

所以

\[x=y^{'} \]

\[y=(x^{'}-y^{'}\lfloor\frac{a}{b}\rfloor) \]

到这里可能有的小盆友就要说了,那\(x^{'}\),\(y^{'}\)怎么求呢?
答:继续设\(x^{''}\)\(y^{''}\)
就这样不断递归,直到\(b=0\),这个时候\(x=1\),\(y=0\)就是一组解。
搞定!
模版题:P5656 【模板】二元一次不定方程 (exgcd)

\(code:\)

//为什么要攀登?因为山就在那里。 
#include<bits/stdc++.h>
#define byn 0x7f7f7f7f7f7f7f7f
#define int long long
using namespace std;
const int mod=1000000007;
int a,b,c,_,x,y;
inline int read(){
	char ch=getchar();
	int x=0,f=1;
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
  	while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
  	return x*f;	
}
inline void write(int x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
    return;
}
inline void exgcd(int a,int b){
	if(!b){
		x=1,y=b;
		return;
	}
	exgcd(b,a%b);
	int lx=x;
	x=y,y=lx-a/b*y;
}
signed main(){
	cin>>_;
	while(_--){
		cin>>a>>b>>c;
		x=0,y=0;
		int gcd=__gcd(a,b);
		if(c%gcd) printf("-1\n");
		else{
			exgcd(a,b);
			int numy=a/gcd,numx=b/gcd; 
			x=x*(c/gcd),y=y*(c/gcd);
			int k=ceil((1.0-x)/numx);
			x+=numx*k;
			y-=numy*k;
			if(y<=0){
				int miny=y+numy*ceil((1.0-y)/numy);
				printf("%lld %lld\n",x,miny);
			}else{
				printf("%lld ",(y-1)/numy+1);
				printf("%lld ",x);
				printf("%lld ",(y-1)%numy+1);
				printf("%lld ",x+(y-1)/numy*numx);
				printf("%lld\n",y);
			}
		}
	}
	return 0;
}

唯一分解定理

对于任意的正整数\(n\),我们可以将其分解成若干素数的乘积。

\[n=p_1^{c_1}p_2^{c_2}……p_k^{c_k} \]

其实这算不上一种正经的算法。
但是这可以给我们提供一种新的看待\(lcm\),\(gcd\)的视角。
对于\(lcm(a,b)\)我们只需要将\(a\)\(b\)的每一个\(c_i\)都取\(max\)
\(gcd(a,b)\)则可以直接取\(min\)

唯一分解定理的力量

1.\(a\mid bc \Rightarrow \frac{a}{gcd(a,b)}\mid c\)
2.\(gcd(a,b)lcm(a,b)=ab\)
3.\(d\mid a\),\(d\mid b\Longleftrightarrow d\mid gcd(a,b)\)
4.\(a\mid d\),\(b\mid d\Longleftrightarrow lcm(a,b)\mid d\)
可以尝试用唯一分解定理理解一下上述结论。

同余

如果\(a\bmod m=b\bmod m\),那么我们称作\(a\)\(b\)\(m\)同余,记作\(a\equiv b(\bmod m)\)
同余的基本性质:
1.\(a\equiv b(\bmod m)\Leftrightarrow a\pm c\equiv b\pm c(\bmod m)\)
2.\(a\equiv b(\bmod m)\Leftrightarrow ac\equiv bc(\bmod m)\)
在模\(m\)的意义下我们不区分\(-1\)\(m-1\),因为它们模\(m\)的结果是一样的。
更进一步,在模\(7\)的意义下,我们也不区分\(\frac{1}{2}\)\(4\),因为它们乘\(2\)的结果模\(7\)都是一样的。
可以看出,对于取模,我们只关心结果,对于模\(m\),我们的世界里只有\(0,1,2,……,m-1\)这几个元素,其他元素和他们是等价的。

逆元

首先\(b\)的逆元要满足一个性质:\(b^{-1}*b\equiv 1(\bmod m)\)
也就是说,我们假设\(x\)\(b\)的逆元,那么一定有\(x*b\equiv 1(\bmod m)\)
我们如何保证一个数\(b\)存在在\(\bmod m\)意义下的逆元呢,当且仅当\(b\)\(m\)互质时,存在逆元。
对于求逆元,我们有以下几种方法:
(以下部分定理笔者就不进行证明了,主要是我也不太会证,感兴趣的可以在网上搜搜看)

  • 1.费马小定理

对于任意一个质数\(p\),如果\(a\),\(p\)互质,那么\(a^{p-1}\equiv 1(\bmod p)\)
之后,我们就可以推出\(a^{p-2}\equiv a^{-1}(\bmod p)\)
这里很明显要用到快速幂,粘一个代码。
\(code:\)

int ksm(int a,int b,int mod){
	int ans=1;
	while(b){
		if(b&1) ans*=a,ans=(ans%mod+mod)%mod;
		a*=a,a=(a%mod+mod)%mod,b>>=1; 
	}
	return ans;
}
  • 2.1欧拉函数

对于一个自然数\(m\),如果\(a\),\(m\)互质,那么\(a^{\varphi(m)}\equiv 1(\bmod m)\)
之后,我们可以看出\(a^{-1}\equiv a^{\varphi(m)-1}(\bmod m)\)
所以,\(a^{\varphi(m)-1}\)就是\(a\)的逆元
*注:对于一个质数\(p\)\(\varphi(p)=p-1\),可以看出,费马小定理是欧拉函数的特殊情况。
但是欧拉函数怎么求呢?
别着急,继续往下看
先来看一下定义:\(\varphi(p)\)表示\(<p\)的正整数中与\(p\)互质的数的个数。
很显然的,如果\(p\)是质数,那么\(\varphi(p)=p-1\)
然后可以证明\(\varphi(p^k)=p^k-p^{k-1}\)
因为\(p\)是质数,对\(p^k\)其进行质因数分解,一定会分成\(k\)\(p\)相乘的形式。
只有不包含任意一个\(p\),才和\(p^k\)互质。
而包含\(p\)的数则有\(p^{k-1}\)个。
所以\(\varphi(p^k)=\frac{p-1}{p}*p^k\)
这里需要引入一个性质,就是\(\varphi(a*b)=\varphi(a)*\varphi(b)\)
证明需要用到中国剩余定理,这里就不展开说了。(以后有时间笔者尽量补)
那么对于一个数\(n\),将其进行质因数分解,根据唯一分解定理,我们一定可以将\(n\)分成如下形式。

\[\begin{align*} \varphi(n)&=\varphi(p_1^{k_1}*p_2^{k_2}*……*p_x^{k_x})\\ &=p_1^{k_1}*p_2^{k_2}*……*p_x^{k_x}*\frac{p_1-1}{p_1}*\frac{p_2-1}{p_2}*……*\frac{p_x-1}{p_x} \end{align*} \]

我们发现前面的高次项就是\(n\),所以\(\varphi(n)=n*\frac{p_1-1}{p_1}*\frac{p_2-1}{p_2}*……*\frac{p_x-1}{p_x}\)
之后就可以通过枚举\(n\)的质因数用\(O(\sqrt n)\)写代码了。
\(code:\)

inline int euler(int x){
    int ans=x;
    for(int i=2;i*i<=x;i++){
        if(x%i==0){
            ans=ans/i*(i-1);
            while(x%i==0) x/=i; 
        }
    }
    if(x>1) ans=ans/x*(x-1);
    return ans;
}
  • 2.2扩展欧拉定理

很简单,给你三个正整数,\(a,m,b\),你需要求:\(a^b \bmod m\)
如果\(b\)非常大的话,那么快速幂的时间复杂度就显然不达标了。
我们需要缩小指数,而扩展欧拉定理就能解决这个问题。
扩展欧拉定理是什么呢?
\(b\ge \varphi(m)\)那么\(a^b \equiv a^{b \bmod\varphi(m)+\varphi(m)}(\mod m)\)
怎么证呢?
我不会……,感兴趣的读者可以自己搜索。
模版题P5091 【模板】扩展欧拉定理
\(code:\)

//为什么要攀登?因为山就在那里。
#include<bits/stdc++.h>
#define byn 0x7f7f7f7f7f7f7f7f
#define int long long
using namespace std;
int a,m,b,ans,mod;
inline int euler(int x){
    int ans=x;
    for(int i=2;i*i<=x;i++){
        if(x%i==0){
            ans=ans/i*(i-1);
            while(x%i==0) x/=i; 
        }
    }
    if(x>1) ans=ans/x*(x-1);
    return ans;
}
inline int read(){
	char ch=getchar();
	int num=0,flag=0;
	while(ch>'9'||ch<'0') ch=getchar();
	while(ch>='0'&&ch<='9'){
		num=num*10+ch-'0';
		if(num>=mod) num%=mod,flag=1;
		ch=getchar();
	}
	if(flag) return num+mod;
	else return num;
}
inline int ksm(int a,int b,int mod){
	int ans=1;
	while(b){
		if(b&1) ans*=a,ans%=mod;
		a*=a,a%=mod,b>>=1;
	}
	return ans;
}
signed main(){
	cin>>a>>m;
	mod=euler(m);
	b=read();
	ans=ksm(a,b,m);
	cout<<ans;
	return 0;
}

来看一道题:给定 \(n\) 个正整数 \(a_i\) ,求它们在模质数 \(p\) 意义下的乘法逆元。
这道题怎么做?
首先第一个想法就是直接用费马小定理,时间复杂度\(O(n \log p)\)
这很明显不能满足我们的要求。
为了减少时间复杂度,我们可以采用一种很巧妙的方法。
对于要求出逆元的数组\(a_1,a_2,……,a_n\)
我们设

\[f_x=a_1*a_2*……*a_x \]

\[g_x=\frac{1}{f_x} \]

那么便有

\[\begin{align*} g_i&=\frac{1}{a_1*a_2*……*a_i}\\ &=\frac{1}{a_1*a_2*……*a_{i+1}}*a_{i+1}\\ &=g_{i+1}*a_{i+1} \end{align*} \]

\[\begin{align*} \frac{1}{a_i}&=\frac{1}{a_1*a_2*……*a_{i}}*a_1*a_2*……*a_{i-1}\\ &=g_i*f_{i-1} \end{align*} \]

由于\(f\)数组我们可以直接\(O(n)\)求出来,如果得到了\(g_n\),那么也可以\(O(n)\)求出\(g\)数组。
\(g_n\)就是\(f_n\)的逆元,直接费马小定理就行了。
费马小定理的时间复杂度是\(O(\log p)\),所以这种方法的时间复杂度就是\(O(n+\log p)\)
模板题P5431乘法逆元 2
\(code\):

//为什么要攀登?因为山就在那里。
#include<bits/stdc++.h>
#define byn 0x7f7f7f7f7f7f7f7f
#define int long long
using namespace std;
int n,p,k,a[5000010],f[5000010],g[5000010],ans;
inline int read(){
	int num=0;
	char ch=getchar();
	while(ch>'9'||ch<'0') ch=getchar();
	while(ch<='9'&&ch>='0'){
		num=num*10+ch-'0';
		ch=getchar();
	}
	return num;
}
inline int ksm(int a,int b,int mod){
	int ans=1;
	while(b){
		if(b&1) ans*=a,ans=(ans%mod+mod)%mod;
		a*=a,a=(a%mod+mod)%mod,b>>=1;
	}
	return ans;
}
signed main(){
	n=read(),p=read(),k=read();
	f[0]=1;
	for(int i=1;i<=n;i++) a[i]=read(),f[i]=f[i-1]*a[i],f[i]=f[i]%p;
	g[n]=ksm(f[n],p-2,p);
	for(int i=n-1;i>=1;i--) g[i]=g[i+1]*a[i+1],g[i]=g[i]%p;
	int num=1;
	for(int i=1;i<=n;i++){
		num*=k;
		num=num%p;
		ans+=num%p*f[i-1]%p*g[i]%p;
	}
	cout<<ans%p;
	return 0;
}
  • 4.线性预处理序列逆元2(不常用)(参考自二两碘酊的博客
    \(p=ki+r\),那么\(k=\lfloor \frac{p}{i}\rfloor\),\(r=p \bmod i\)
    那么

\[ki+r\equiv 0(\bmod p) \]

\[ki\equiv -r(\bmod p) \]

\[\frac{r}{i}\equiv -k(\bmod p) \]

\[\frac{1}{i}\equiv -\frac{k}{r} (\bmod p) \]

因为

\[p\equiv 0(\bmod p) \]

\[p-k\equiv -k(\bmod p) \]

\[\frac{p-k}{r}\equiv-\frac{k}{r}(\bmod p) \]

所以

\[\frac{1}{i}\equiv \frac{p-k}{r} (\bmod p) \]

\(code:\)

void work(){
    ele[1]=1;
	for(int i=2;i<=n;i++){
		int k=p/i,r=p%i;
		ele[i]=(p-k)%p*ele[r]%p;
	}
}
  • 5.扩展欧几里得

根据逆元定义:

\[a*x\equiv 1(\bmod p) \]

那么\(x\)就是\(a\)的逆元。
对于任意的整数\(k\)

\[k*p+1\equiv 1(\bmod p) \]

\[k*p+a*x\equiv 1(\bmod p) \]

根据逆元的定义,\(a\)\(p\)互质。
所以方程

\[k*p+a*x=1 \]

一定有整数解。
根据上文提到的扩展欧几里得,直接就可以求出\(x\)的值。
这真是太棒了,即便\(p\)不是质数我们依旧可以这么写,虽然可能略慢于费马小定理,但是用它就对了。
代码和上面一样,就不再给出了。

拉格朗日插值

对于 \(n\) 个点 \((x_i,y_i)\),如果满足 \(\forall i\neq j, x_i\neq x_j\),那么经过这 \(n\) 个点可以唯一地确定一个 \(n-1\) 次多项式 \(y = f(x)\)
现在,给你如上所述的\(n\)个点,让你求出\(f(k)\)的值是多少。
对于每个坐标\((x_i,y_i)\),我们希望构造一个函数\(G_i(k)\),对于这个函数我们希望只有当\(k=x_i\)\(G_i(k)\)的值为\(y_i\),其他时候此函数的值均为\(0\)
这样我们就可以通过给所有坐标的\(G_i(x)\)函数的取值求和来求出我们想要的值。

\[f(k)=\sum_{i=1}^{n}G_i(k) \]

但是写出\(G_i(k)\)函数的表达式也并不简单,我们继续简化这个问题。
对于坐标\((x_i,y_i)\),构造函数\(D_i(k)\),当\(k\neq x_i\)时此函数值均为\(0\)
这个表达式我们就可以尝试写出来了

\[D_i(k)=\prod^{n}_{j=1,j\neq i}k-x_j \]

显然,当\(k\neq x_i\)时,表达式中一定会出现一个\(0\)导致函数的值为\(0\)
而对于\(k=x_i\)的情况,虽然\(D_i(k)\neq y_i\),但是我们只要将所有的\(D_i\)都除去\(D_k(k)\)再乘上\(y_i\)就可以了。
所以

\[\begin{align*} G_i(k)&=y_i\frac{D_i(k)}{D_k(k)}\\ &=y_i\frac{\prod^{n}_{j=1,j\neq i}k-x_j}{\prod^{n}_{j=1,j\neq k}k-x_j}\\ \end{align*} \]

\(k\neq x_i\)的时候上面的式子一定是\(0\),所以下面的分母是多少都无所谓。
我们来看\(k=x_i\)的情况。
此时\(k=x_i\),我们直接把分母的\(k\)替换掉。

\[y_i\frac{\prod^{n}_{j=1,j\neq i}k-x_j}{\prod^{n}_{j=1,j\neq k}k-x_j}=y_i\frac{\prod^{n}_{j=1,j\neq i}k-x_j}{\prod^{n}_{j=1,j\neq k}a_i-x_j}\\ \]

同理连乘符号我们也可以直接提出来。

\[y_i\frac{\prod^{n}_{j=1,j\neq i}k-x_j}{\prod^{n}_{j=1,j\neq k}a_i-x_j}\\=y_i\prod^{n}_{j=1,j\neq i}\frac{k-x_j}{x_i-x_j} \]

所以

\[G_i(k)=y_i\prod^{n}_{j=1,j\neq i}\frac{k-x_j}{x_i-x_j} \]

合并一下

\[f(k)=\sum_{i=0}^{n}(y_i\prod^{n}_{j=1,j\neq i}\frac{k-x_j}{x_i-x_j}) \]

模板题P4781 【模板】拉格朗日插值

\(code:\)

//为什么要攀登?因为山就在那里。 
#include<bits/stdc++.h>
#define byn 0x7f7f7f7f7f7f7f7f
#define int long long
using namespace std;
const int mod=998244353;
int ans,n,k,x[20010],y[20010];
int ksm(int a,int b,int p){
	int ans=1;
	while(b){
		if(b&1) ans*=a,ans=(ans%p+p)%p;
		a*=a,a=(a%p+p)%p,b>>=1;
	}
	return ans;
}
signed main(){
	cin>>n>>k;
	for(int i=1;i<=n;i++) cin>>x[i]>>y[i];
	for(int i=1;i<=n;i++){
		int num1=1,num2=1;
		for(int j=1;j<=n;j++){
			if(j==i) continue;
			num1=(num1*(k-x[j])%mod+mod)%mod;
			num2=(num2*(x[i]-x[j])%mod+mod)%mod;
		}
		ans=ans+num1%mod*ksm(num2,mod-2,mod)%mod*y[i];
		ans=(ans%mod+mod)%mod;
	}
	cout<<ans;
	return 0;
}

如果给出的数据比较核善,保证每个\(x_i=i\),那么我们就可以优化一下我们的时间复杂度。
首先将每个\(x\)替换。

\[f(k)=\sum_{i=0}^{n}(y_i\prod^{n}_{j=1,j\neq i}\frac{k-j}{i-j}) \]

\(p_i=\prod^i_{j=1}k-j\),\(s_i=\prod^i_{j=i+1}k-j\)
那么

\[f(k)=\sum_{i=0}^{n}(y_i\frac{p_{k-1}s_k}{\prod^{n}_{j=1,j\neq i}i-j}) \]

但是分母那一坨怎么办?
先打开看看。

\[\begin{align*} \prod^{n}_{j=1,j\neq i}i-j&=(-1)^{n-i}*1*2*……*(i-1)*1*2*……*(n-i)\\ &=(-1)^{n-i}(i-1)!(n-i)! \end{align*} \]

直接预处理一个\(fac\)阶乘数组就可以了。
很明显,对于\(s\),\(p\),\(fac\)这三个数组我们都可以\(O(n)\)预处理解决。
最后再用\(O(n)\)求和就可以了。
总时间复杂度\(O(n)\)
模版题:CF622F The Sum of the k-th Powers

但是目前洛谷交不了,我也没写,所以没有代码,嘻嘻~。

中国剩余定理(CRT)

试求如下同余方程组的最小非负整数解:

\[\begin{cases} x\equiv a_1(\bmod b_1)\\ x\equiv a_2(\bmod b_2)\\ ……\\ x\equiv a_n(\bmod b_n) \end{cases} \]

确保\(b_i\)两两互质。
怎么解决这道题呢。
我们简化一下此题目。
分别求解以下方程:

\[\begin{cases} x_1\equiv a_1(\bmod b_1)\\ x_1\equiv 0(\bmod b_2)\\ ……\\ x_1\equiv 0(\bmod b_n) \end{cases} \]

\[\begin{cases} x_2\equiv 0(\bmod b_1)\\ x_2\equiv a_2(\bmod b_2)\\ ……\\ x_2\equiv 0(\bmod b_n) \end{cases} \]

\[…… \]

\[\begin{cases} x_n\equiv 0(\bmod b_1)\\ x_n\equiv 0(\bmod b_2)\\ ……\\ x_n\equiv a_n(\bmod b_n) \end{cases} \]

此时,我们将每个方程的解相加,原方程的解就是

\[x=x_1+x_2+……+x_n \]

对于这一坨方程,我们首先尝试解其中的一个:

\[\begin{cases} x_1\equiv a_1(\bmod b_1)\\ x_1\equiv 0(\bmod b_2)\\ ……\\ x_1\equiv 0(\bmod b_n) \end{cases} \]

要求出如上方程的解,我们可以进一步装换:

\[\begin{cases} y_1\equiv 1(\bmod b_1)\\ y_1\equiv 0(\bmod b_2)\\ ……\\ y_1\equiv 0(\bmod b_n) \end{cases} \]

那么\(x_1=y_1*a_1\)
观察同余方程组,可以发现\(y_1\)一定是\(\prod^n_{i=2}b_i\)的倍数。
我们设\(\prod^n_{i=2}b_i=m_1\)
那么\(y_1=m_1*k_1\)
由于\(y_1\)还要满足第一个同余方程。
所以\(m_1*k_1\equiv 1(\bmod b_1)\)
这时候我们发现\(k_1\)就是\(m_1\)\(\bmod b_1\)的意义下的逆元,直接扩展欧几里得就可以求出。
按照如上方法,我们就求出了上述\(n\)个同余方程的整数解,将它们加起来,我们就得到了原方程的整数解。
而最小非负整数解就是用你得到的解对所有\(b\)的乘积取模。
模板题P1495 【模板】中国剩余定理(CRT)/ 曹冲养猪

\(code:\)

//为什么要攀登?因为山就在那里。
#include<bits/stdc++.h>
#define byn 0x7f7f7f7f7f7f7f7f
#define int long long
using namespace std;
int n,a[20],b[20],num=1,x,y,ans;
inline int ksm(int a,int b,int mod){
	int ans=1;
	while(b){
		if(b&1) ans*=a,ans%=mod;
		a*=a,a%=mod,b>>=1;
	}
	return ans;
}
inline void exgcd(int a,int b,int &x,int &y){
	if(!b){
		x=1,y=0;
		return ;
	}
	exgcd(b,a%b,x,y);
	int lx=x;
	x=y,y=lx-a/b*y;
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i]>>b[i],num*=a[i];
	for(int i=1;i<=n;i++){
		int m=num/a[i];
		exgcd(m,a[i],x,y);
		ans=(__int128)ans+(__int128)x%num*m%num*b[i]%num;
		ans%=num;
	}
	cout<<(ans%num+num)%num;
	return 0;
}

扩展中国剩余定理(exCRT)

还是一样的问题:
试求如下同余方程组的最小非负整数解:

\[\begin{cases} x\equiv a_1(\bmod b_1)\\ x\equiv a_2(\bmod b_2)\\ ……\\ x\equiv a_n(\bmod b_n) \end{cases} \]

但是不保证\(b\)互质了,这是一个悲伤的故事。
乍一看可能有一点难。
我们先考虑只有两个同余方程怎么求解。

\[\begin{cases} x\equiv a_1(\bmod b_1)\\ x\equiv a_2(\bmod b_2) \end{cases} \]

显然\(x\)满足如下两个方程。

\[x=k_1b_1+a_1 \]

\[x=k_2b_2+a_2 \]

将两个方程相减。

\[k_2b_2-k_1b_1=a_2-a_1 \]

由于\(a_1\),\(a_2\),\(b_1\),\(b_2\)已知,所以我们可以直接通过扩展欧几里得求出方程的特解。
因为题目要的是最小正整数解。
显然当\(k_2\)是正整数且最小时\(x\)满足条件。
所以我们对求出\(k_2\)\(b_1\)的结果就能保证\(x\)是最小的正整数解(具体怎么做详见上文扩展欧几里得算法(exgcd))。
之后我们考虑怎么合并这两个同余方程。
既然\(x\)已经求出那么我们要处理的就只剩下\(a\)\(b\)
对于合并后的\(a\),我们直接用两个方程的解\(x\)代替即可(应该很显然吧)。
而对于\(b\)我们直接取\(lcm(b_1,b_2)\)就可以了。
合并后的方程:

\[x_i \equiv k_2b_2+a_2(\bmod lcm(b_1,b_2)) \]

假设我们已经合并了\(k-1\)个方程。
\(m_i=LCM_{j=1}^i b_j\)
我这么写应该能看懂吧……
所以最终的方程就是:

\[\begin{cases} x\equiv x_k(\bmod m_k)\\ x\equiv a_{k+1}(\bmod b_{k+1})\\ \end{cases} \]

\(x_k\)表示合并了\(k\)个同余方程之后的解。
就这样一直合并,直到只剩一个方程就是我们要的结果了。
模板题P4777 【模板】扩展中国剩余定理(EXCRT)

\(code:\)

//为什么要攀登?因为山就在那里。
#include<bits/stdc++.h>
#define byn 0x7f7f7f7f7f7f7f7f
#define int __int128
using namespace std;
int n,a[100010],b[100010],now1,ans1,x,y;
inline int read(){
	char ch=getchar();
	int num=0;
	while(ch>'9'||ch<'0') ch=getchar();
	while(ch>='0'&&ch<='9'){
		num=num*10+ch-'0';
		ch=getchar();
	}
	return num;
}
inline void write(int num){
	if(num==0) return ;
	if(num<0){
		num*=-1;
		putchar('-');
	}
	char ch=num%10+'0';
	write(num/10);
	putchar(ch);
}
inline void exgcd(int a,int b,int &x,int &y){
	if(!b){
		x=1,y=0;
		return ;
	}
	exgcd(b,a%b,x,y);
	int lx=x;
	x=y,y=lx-a/b*y;
}
signed main(){
	n=read();
	for(int i=1;i<=n;i++) a[i]=read(),b[i]=read();
	now1=a[1],ans1=b[1];
	for(int i=2;i<=n;i++){
		int now2=a[i],ans2=b[i],c=ans2-ans1;
		int g=__gcd(now1,now2);
		if(c%g){
			cout<<-1;
			return 0;
		}
		exgcd(now1,now2,x,y);
		x=x*(c/g);
		x=(x%now2+now2)%now2;
		ans1+=x*now1;
		now1=now1/g*now2;
		ans1=(ans1%now1+now1)%now1;
	}
	write(ans1);
	return 0;
}

2025.05.02上午

线性代数的基础知识

一些定义

  • 1.矩阵:\(nm\)个数字,写成\(n\)行,\(m\)列,就是一个\(n*m\)的矩阵。
  • 2.向量:\(n*1\)的矩阵成为列向量,\(1*n\)的矩阵称为行向量,当然\(n*1\)的矩阵也可看做数组。
  • 3.矩阵的表示通常使用大写字母。
    例如:矩阵\(A\)的第\(i\)行,第\(j\)列用\(a_{i,j}\)(或\(A_{i,j}\))表示。
  • 4.对角线/主对角线:\(a_{1,1},a_{2,2},a_{3,3}……\)这些元素,也就是从左上到右下的一条倾斜\(45°\)的斜线。
  • 5.反对角线:从右上往左下的,一条倾斜\(45°\)的斜线。

矩阵的运算

  • 1.加减法:(大小相同的矩阵)按位相加

\[A+B=C \Longleftrightarrow a_{i,j}+b_{i,j}=c_{i,j} \]

  • 2.乘上常数:

\[(cA_{i,j})=c*A_{i,j} \]

  • 3.转置:把矩阵沿对角线对称

数字表示:\((A^T)_{i,j}=A_{i,j}\)
例:

\[A= \begin{bmatrix} 1 & 2 & 3 \\ 6 & 5 & 4 \end{bmatrix} \]

\[A^T= \begin{bmatrix} 1 & 6 \\ 2 & 5 \\ 3 & 4 \end{bmatrix} \]

  • 4.乘法:不再是按位相乘

一个\(a*b\)的矩阵和一个\(b*c\)的矩阵,相乘会得到一个\(a*c\)的矩阵。
定义:

\[A*B=C\Longleftrightarrow C_{i,j}=\sum^n_{k=1}A_{i,k}B_{i,k} \]

\(code:\)

memset(C,0,sizeof(C));
for(int i=1;i<=a;i++)
	for(int j=1;j<=c;j++)
		for(int k=1;k<=b;k++)
			C[i][j]+=A[i][k]*B[k][j];
  • 5.另一种看待矩阵乘法的方法:

向量的点积:两个\(n\)维向量的点积:

\[\vec{a}\cdot\vec{b}=\sum^n_{i=1}a_ib_i \]

把矩阵\(A\)看成\(a\)\(b\)维向量组成的矩阵。
把矩阵\(B\)看成\(c\)\(b\)维向量组成的矩阵。
那么,\(C=A*B\)就是\(a*c\)个点积结果排成的矩阵。
具体地,\(C_{i,j}\)就是\(A\)的第\(i\)个⾏向量,和\(B\)的第\(j\)个列向量,点积的结果。
我们由此可以看出矩阵乘法的一个性质:

\[(AB)^T=B^TA^T \]

矩阵运算的性质

  • 1.交换律:\(A\circ B=B\circ A\)
  • 2.结合律:\((A\circ B)\circ C=A\circ(B\circ C)\)
    加法很明显满足结合律和交换律。
    乘法满足结合律但是不满足交换律。
    也就是说\(A*B\)\(B*A\)不一定相等。
    乘法满⾜对加法的分配律:

\[A*(B+C)=AB+AC \]

线性空间和线性基

"空间"

全体\(n\)维向量组成的集合,称作\(n\)维“空间”。
1维空间:数轴。
2维空间:平⾯直⻆坐标系(建系!多好的建系条件啊!)。
我们可以把熟悉的 维空间中的点,和 维向量做⼀个⼀⼀对应,因此我们也经常混⽤“点”和“向量”。

线性空间

我们可以发挥我们的想象⼒:空间内的点⽆法脱离这个空间。
数学上的定义:⼀个向量的集合\(V\)是线性空间,当且仅当

\[\forall \vec{x},\vec{y}\in V \]

都有

\[\vec{x}+\vec{y}\in V \]

\[k\vec{x}\in V \]

换句话说,⽆论怎么“组合”两个向量,还是“拉伸”⼀个向量,还是先拉伸再组合,得到的向量都还是在这个空间中。

线性组合

对于\(n\)个向量\(\vec{v_1},\vec{v_2},……,\vec{v_n}\),以下向量是他们的一种线性组合:

\[c_1\vec{v_1}+c_2\vec{v_2}+……+c_n\vec{v_n} \]

其中\(c_1,c_2,……,c_n\)是常数。我们称\(c_1,c_2,……,c_n\)是这个线性组合的系数。

  • 任⼀个线性组合,都能通过“拉伸”和“组合”得到。
  • 反过来,从\(n\)个向量出发,⽆论怎么做“拉伸”和“组合”,得到的都还是这\(n\)个向量的某种线性组合。

张出(span)和线性基

我们已经能感受到线性组合和线性空间的关系了。事实上,只要把刚刚的内容总结⼀下,我们⽴刻能发现:

  • 任取\(n\)个向量\(\vec{v_1},\vec{v_2},……,\vec{v_n}\),它们的全体线性组合,都构成了一个线性空间\(V\)。我们记\(V=span(\vec{v_1},\vec{v_2},……,\vec{v_n})\)为这\(n\)个向量张出的线性空间。

反过来说对不对呢?也就是说:

  • 任取⼀个线性空间,它都可以被描述成某\(n\)个向量的全体线性组合。

这个命题也是正确的。这样的\(n\)个向量,称为这个线性空间的⼀组⽣成元。
如果⼀组⽣成元是最⼩的(也就是说,没有⼀组个数上⽐它更少的⽣成元),它就是这个空间的⼀组线性基(或简称基)。
一个方便理解线性代数的视频。此视频共6节,强烈建议各位读者观看。

基坐标

⼀个空间可以很复杂,但是只要知道了它的⼀组基,我们就可以描述这个空间。同时,我们还可以⽤这组基来描述这个空间的每⼀个向量。
定理:对于一个线性空间\(V\),如果\(\vec{v_1},\vec{v_2},……,\vec{v_n}\)是它的一组基,则对于这个空间内的任意一个向量\(\vec{x}\in V\),都存在唯一的系数\(c_1,c_2,……,c_n\),使得:

\[x=c_1\vec{v_1}+c_2\vec{v_2}+……+c_n\vec{v_n} \]

这组系数\(c_1,c_2,……,c_n\)就称作\(\vec{x}\)\(\vec{v_1},\vec{v_2},……,\vec{v_n}\)这组基下的坐标。
我们平时⽤的是⼀组特殊的基来描述\(n\)维空间,这组基中的向量称为单位向量,第\(i\)个单位向量是\(e_i=(0,0,…,1,…,0)\)(只有第\(i\)位是1)。

线性变换

线性空间中的变换

变换:是这个空间中的函数,把⼀个向量映射到另⼀个向量。
线性变换:满⾜线性性的变换。

\[f(\vec{x}+\vec{y})=f(\vec{x})+f(\vec{y}) \]

\[f(k\vec{x})=kf(\vec{x}) \]

发现:\(\vec{0}\)必须被映射到\(\vec{0}\)
⼆维空间中熟悉的线性变换:绕原点旋转;沿x轴对称……
一个方便理解线性变换的视频。

线性变换和矩阵

事实上,每⼀个线性变换都可以唯⼀地⽤⼀个矩阵表⽰。
每一个线性变换\(f()\)都可以表示成\(f(\vec{x})=A*\vec{x}\),其中\(A\)是一个矩阵。
线性变换的复合:假如\(f(\vec{x})=A*\vec{x}\)\(g(\vec{x})=B*\vec{x}\),那么:

\[f(g(\vec{x}))=A*(B*\vec{x})=AB\vec{x} \]

这也是为什么矩阵乘法为什么定义的与我们的常识不太相同,因为只有这样定义,才能⽤矩阵描述线性变换,⽤矩阵乘法描述线性变换的复合。

2025.05.03下午

大步小步算法(Baby Step Giant Step)

此算法解决的问题大约是给定整数\(a\),\(b\),质数\(p\),求满足\(a^x\equiv b(\bmod p)\)中的非负整数\(x\)的最小值。
对于一个模数\(p\)\(a^0,a^1,……,a^p-1\)一定存在一次或多次对于\(a^i\bmod p\)的结果的循环(因为一个数对\(p\)取模最多有\(p\)种结果)。
因此,我们令\(t=\lfloor\sqrt p\rfloor\),\(0<j \le t-1\)
\(x=i*t-j\),那么\(a^{i*t-j}\equiv b(\bmod p)\)
两边同乘\(a^j\),\(a^{i*t}\equiv b*a^j(\bmod p)\)
之后我们就可以用\(t\)次枚举\(j\),然后把\(b*a^j\)存到\(map\)中。
之后枚举\(i\),因为前面已经说了,对于一个模数\(p\)\(a^0,a^1,……,a^p-1\)一定存在一次或多次对于\(a^i\bmod p\)的结果的循环。
所以我们就应该保证\(i*t-j<p\),那么便得出\(i\)的范围是\([0,t]\),这样我们就计算出了\(a^{i*t}\)
我们查询这个数的值在\(map\)中是否存在对应的\(j\),如果存在,因为\(t\)已知,那么便得出了答案。
时间复杂度\(O(t)\),也就是\(O(\sqrt p)\)
模板题p3846可爱的质数
\(code:\)

//为什么要攀登?因为山就在那里。 
#include<bits/stdc++.h>
#define byn 0x7f7f7f7f7f7f7f7f
#define int long long
using namespace std;
int p,a,b;
unordered_map<int,int> mp;
int ksm(int a,int b,int mod){
	int ans=1;
	while(b){
		if(b&1) ans*=a,ans=(ans%mod+mod)%mod;
		a*=a,a=(a%mod+mod)%mod,b>>=1; 
	}
	return ans;
}
int dbxb(int a,int b,int p){
	int x=ceil(sqrt(p)),now=1;
	for(int i=0;i<x;i++){
		int y;
		if(!i) y=(b%p+p)%p;
		else{
			now*=a;
			now=(now%p+p)%p;
			y=((b*now)%p+p)%p;
		}
		mp[y]=i;
	}
	now=ksm(a,x,p);
	int val=1;
	for(int i=1;i<=x;i++){
		val*=now;
		val=(val%p+p)%p;
		if(mp.find(val)!=mp.end()){
			int j=mp[val];
			if(i*x>=j) return ((i*x-j)%p+p)%p;
		}
	}
	return -1;
}
signed main(){
	cin>>p>>a>>b;
	b%=p;
	int ans=dbxb(a,b,p);
	if(ans==-1) cout<<"no solution";
	else cout<<ans;
	return 0;
}
posted @ 2025-05-01 20:42  idle-onlooker  阅读(112)  评论(0)    收藏  举报