基础模板清理

筛法求素数

Eratosthenes 筛法

时间复杂度 O(nloglogn)

关键优化:ji×i 开始

void getprime(int mx){
	memset(is_prime, 1, sizeof(is_prime));
	is_prime[0] = is_prime[1] = 0;
	F(i, 2, mx){
		if(is_prime[i]){
			prime[++cnt] = i;//存
			if(1ll * i * i > mx) continue;//判越界
			for(int j = i * i; j <= mx; j+=i) is_prime[j] = 0;//筛
			//因为从 2 到 i - 1 的倍数我们之前筛过了,这里直接从 i 
			//的倍数开始,提高了运行速度		
		}  
	}
}

Euler 筛法

时间复杂度 O(n)

关键优化 imodprime[j]=0break

void getprime(int mx){
	memset(is_prime, 1, sizeof(is_prime));
	is_prime[0] = is_prime[1] = 0;
	F(i, 2, mx){
		if(is_prime[i]){
			prime[++cnt] = i;//存		
		}
		F(j, 1, cnt){
			if(1ll * prime[j] * i > mx) break;
			is_prime[prime[j] * i] = 0;
			if(!(i % prime[j])) break;
	        // i % pri_j == 0
	        // 换言之,i 之前被 pri_j 筛过了
	        // 由于 pri 里面质数是从小到大的,所以 i 乘上其他的质数的结果一定会被
	        // pri_j 的倍数筛掉,就不需要在这里先筛一次,所以这里直接 break
		}  
	}
}

参考博客:

筛法 - OI Wiki (oi-wiki.org)

ST表

易忘:倍增都是外层写指数,内层写起点!

int a[N],st[N][20]; 
void init(){
	F(i, 1, n) st[i][0] = a[i];
	F(j, 1, 20) for(int i = 1; i + (1<<j) - 1 <= n; ++i)
		st[i][j] = max(st[i][j-1], st[i + (1<<(j-1))][j-1]);
}
int ask(int l, int r){
	int t = log2(r - l + 1);
	return max(st[l][t], st[r - (1 << t) + 1][t]);
}

SPFA - 负权回路

关键:

1.建立 虚点 和 虚边

2.cnt[v] = cnt[u] + 1

#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=(r);++i)
#define G(i,r,l) for(int i(r);i>=(l);--i)
using namespace std;
using ll = long long;
const int N = 1e6 + 5;
const int inf = 0x3f3f3f3f;
struct node{
	int v, w, ne;
}e[N<<1];
int first[N], dis[N], cnt[N];
int n, m, idx = 0;
bool ins[N];
inline void add(int x, int y, int z){
	e[++idx] = (node){y, z, first[x]};
	first[x] = idx;
}
bool spfa(int st){
	queue<int> q; 
	q.emplace(st);
	ins[st] = 1;
	cnt[st] = 0;
	memset(dis, 0x3f, sizeof(dis));
	dis[st] = 0;
	while(q.size()){
		int u = q.front(); q.pop(); 
		ins[u] = 0;
		for(int i=first[u];i;i=e[i].ne){
			int v = e[i].v, w = e[i].w;
			if(dis[v] > dis[u] + w){
				dis[v] = dis[u] + w;
				cnt[v] = cnt[u] + 1;
				if(cnt[v] > n) return 0;
				if(!ins[v]) q.emplace(v), ins[v] = 1;
			}
		}
	} 
	return 1;
}
signed main(){
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	int u, v, w;
	cin >> n >> m;
	F(i, 1, n) add(0, i, 0);
	F(i, 1, m) cin >> u >> v >> w, add(u, v, w);
	if(spfa(0)) cout << "No\n";
	else cout << "Yes\n";
	return 0;
}

Borüvka

补充知识-比较算子:

less<int>() : 用在函数里相当于 <

greater<int>() : 用在函数里相当于 >

priority_queue<int, vector<int>, less<int>> :祖先小于儿子 要交换,所以其实是 大根堆

priority_queue<int, vector<int>, greater<int>> :祖先大于儿子 要交换,所以其实是 小根堆

对于 完全图边权与点信息有关 的 MST 问题,通常考虑 Borüvka 算法。

Boruvka 算法由三部分构成:

  • 预处理节点信息(由题目实际情况决定有无)
  • 找节点的最佳出边(best[i]val[i]
  • 集合间连边(多路增广)
	bool tag = 1;
	int update = n-1;
	while(tag){
		tag = 0;
		F(i, 0, n + 1) best[i] = 0, val[i] = 2e9 + 50;
		F(i, 1, m){
			int u = get(x[i]), v = get(y[i]);
			if(u == v) continue;
			if(!best[u] || val[u] > z[i]) best[u] = v, val[u] = z[i];
			if(!best[v] || val[v] > z[i]) best[v] = u, val[v] = z[i];
		}
		F(i, 1, n){
			if(i == get(i) && best[i] && get(i) != get(best[i])){
				--update;
				tag = 1;
				sum += val[i];
				fa[get(best[i])] = get(i);
			}
		}
	}
	if(update>0) cout << "orz\n";
	else cout << sum << '\n';

推荐习题:

C240609C. 最小生成树 - SOJ

Problem - 888G - Codeforces题解

扩展欧几里得算法

ax+by=gcd(a,b)

gcd(a,b)=gcd(b,amodb)

bx+(aa/b×b)y=gcd(b,amodb)

ay+b(xa/b×y)=gcd(b,amodb)

x=y,y=xa/b×y

边界 :a=gcd(a,b),b=0,x=1,y=0.

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;
}

推荐习题:

青蛙的约会

结论:当 c=k×gcd(a,b)ax+by=c(0,b/gcd(a,b)) 内有唯一解。a=a×k

注意:模数必须是正数才能求出正确的 gcd,其它的倒无所谓。

KMP

241002:今天发现一个很舒适的 KMP 写法!

nxt[i] :前缀函数,子串 s[1i] 的最长公共前后缀

nxt[i+1]的几何意义 :预备匹配的下一位。

#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=(r);++i)
#define G(i,r,l) for(int i(r);i>=(l);--i)
using namespace std;
using ll = long long;
const int N = 1e6 + 5;
int nxt[N];
char s[N], t[N];
int n, m;
void getnxt(){
	int k = 0;
	F(i, 2, m){ // 每次 k 就是 nxt[i - 1]
		while(k && t[i] != t[k + 1]) k = nxt[k];
		if(t[i] == t[k + 1]) ++ k;
		nxt[i] = k;
	}
}
bool getans(){
	int k = 0, tag = 0;
	F(i, 1, n){
		while(k && s[i] != t[k + 1]) k = nxt[k];
		if(s[i] == t[k + 1]) ++ k;
		if(k == m){
			cout << i - m + 1 << '\n';
			k = nxt[k];
			tag = 1;
		}
	}
	return tag;
}
signed main(){
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin >> (s + 1) >> (t + 1);
	n = strlen(s + 1), m = strlen(t + 1);
	getnxt();
	if(!getans()) cout << "NO";
	return fflush(0), 0;
}

推荐习题:

子串结构 - SOJ 稍微变形,很适合练手。

参考博客:

前缀函数与 KMP 算法 - OI Wiki (oi-wiki.org)

clock: 默认单位是毫秒。

BSGS

求最小正整数 x,使 axb(modp)a,b,p 给定,范围在 int 以内。

x=i×t+j,我们取 t=mod。移项得:

ait=baj

容易发现 i,t,jmod

所以我们可以预处理所有数对 (baj,j),用哈希表存下来。接着枚举 i,当发现存在 ait 这个键值时,输出对应的 i×tj,可以证明是最小的。

  • 简单证一下:每当 i+1ans 增加 t,而由于 jt,所以必然不优。
#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=(r);++i)
#define G(i,r,l) for(int i(r);i>=(l);--i)
#define int ll
using namespace std;
using ll = long long;
const int inf = 1e18;
map<int, int> s;
int mod, a, b;
int quickmod(ll x, int y){
	ll res = 1;
	while(y){
		if(y & 1) res = res * x % mod;
		y >>= 1;
		x = x * x % mod;
	} return res;
}
signed main(){
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin >> mod >> a >> b;
	if(a == b) return cout << 0, 0;
	int t = __builtin_sqrt(mod);
	int ret = b;
	F(i, 1, t){
		(ret *= a) %= mod;
		if(!s.count(ret)) s[ret] = i;
	}	
	int base = quickmod(a, t), nw = base;
	F(i, 1, t){
		if(s.count(nw)) return cout << i * t - s[nw], 0;
		(nw *= base) %= mod;
	} cout << "no solution";
	return fflush(0), 0;
}

差分约束系统

不管题目怎么变,认准小于号,然后带入 disvdisu+w 对应各个变量。

“等号”全部处理成 ab+cab+c (放缩)。

判负环:cnt[v]=cnt[u]+1>n 就有负环。

#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=(r);++i)
#define G(i,r,l) for(int i(r);i>=(l);--i)
using namespace std;
using ll = long long;
const int N = 1e4 + 5;
const int inf = 0x3f3f3f3f;
struct node{
	int v, w, ne;
}e[N << 1];
int n, m, idx = 0;
int dis[N], first[N], cnt[N];
queue<int> q;
bool vis[N];
void add(int x, int y, int z){
	e[++ idx] = (node){y, z, first[x]};
	first[x] = idx;
}
bool spfa(){
	memset(vis, 0, sizeof(vis));
	memset(dis, 0x3f, sizeof(dis));
	memset(cnt, 0, sizeof(cnt));
	dis[0] = 0;
	q.push(0);
	vis[0] = 1;
	cnt[0] = 0;
	while(q.size()){
		int u = q.front();
		q.pop();
		vis[u] = 0;
		for(int i = first[u]; i; i = e[i].ne){
			int v = e[i].v, w = e[i].w;
			if(dis[v] > dis[u] + w){
				dis[v] = dis[u] + w;
				cnt[v] = cnt[u] + 1;
				if(cnt[v] > n) return 0;
				if(!vis[v]) q.push(v), vis[v] = 1;
			}
		}
	}
	return 1;
}
signed main(){
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin >> n >> m;
	F(i, 1, n) add(0, i, 0);
	F(i, 1, m){
		int v, u, w;
		cin >> v >> u >> w;
		add(u, v, w);
	}
	if(!spfa()) cout << "NO\n";
	else F(i, 1, n) cout << dis[i] << ' ';
	return fflush(0), 0;
}

中国剩余定理

给定 m[i],a[i],求最小正整数 x,使得对于 1in,满足:

xa[i](modm[i])

设:

sum=1inm[i]

M[i]=sum/m[i]

t[i]=M[i]1(modm[i])

则答案为

1ina[i]×t[i]×M[i](modsum)

#include<bits/stdc++.h>
#define int long long
using namespace std;
int m[20], a[20], sum = 1, n;
int exgcd(int a, int b, int &x, int &y){
	if(!b){
		x = 1;
		y = 0;
		return a;
	}
	int d = exgcd(b, a % b, y, x);
	y -= a / b * x;
	return d;
}
signed main(){
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin >> n; for(int i = 1; i <= n; ++ i) cin >> m[i] >> a[i], sum *= m[i];
	int x = 0, y;
	for(int i = 1; i <= n; ++ i){
		int nw = sum / m[i];
		int t;
		exgcd(nw, m[i], t, y);
		t = (t % m[i] + m[i]) % m[i];
		(x += (t * a[i] % sum * nw % sum)) %= sum;
	}
	cout << x << '\n';
	return 0;
}

矩阵加速递推

这个递推得是一个线性变换。

推式子的基本套路就是一个方阵 * 列向量。列向量里一般填递推式里的每一项,因此方阵和列向量的大小都和这个项数相关。

填方阵的时候,核心就是“怎么由当前状态推出下一次状态,并且将当前状态保留”。

二分图匹配

注意每次 dfs 的目的只是为了扩展一条

posted @   superl61  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
点击右上角即可分享
微信分享提示