基础模板清理
筛法求素数
Eratosthenes 筛法
时间复杂度
关键优化:
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 筛法
时间复杂度
关键优化
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
}
}
}
参考博客:
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';
推荐习题:
Problem - 888G - Codeforces ( 题解 )
扩展欧几里得算法
边界 :
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;
}
推荐习题:
结论:当
注意:模数必须是正数才能求出正确的
KMP
241002:今天发现一个很舒适的 KMP 写法!
#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
求最小正整数
令
容易发现
所以我们可以预处理所有数对
- 简单证一下:每当
, 增加 ,而由于 ,所以必然不优。
#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;
}
差分约束系统
不管题目怎么变,认准小于号,然后带入
“等号”全部处理成
判负环:
#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;
}
中国剩余定理
给定
设:
则答案为
#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
的目的只是为了扩展一条边
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)