基础模板清理

筛法求素数

Eratosthenes 筛法

时间复杂度 \(O(n loglogn)\)

关键优化:\(j\)\(i \times 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)\)

关键优化 \(i \mod prime[j]=0\)\(break\)

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, a \mod b)\)

\(bx' + (a - a/b \times b) y' = gcd(b,a \mod b)\)

\(ay' + b(x' - a/b \times y') = gcd(b, a \mod b)\)

\(x = y', y = x' - a/b \times 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\times \gcd(a, b)\)\(ax + by = c\)\((0, b / \gcd(a, b))\) 内有唯一解。\(a' = a \times k\)

KMP

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

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

\(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;
char a[N], b[N] ;
int nxt[N];
signed main(){
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin >> (a + 1) >> (b + 1);
	int n = strlen(a + 1), m = strlen(b + 1);
	F(i, 2, m){
		int k = nxt[i - 1];
		while(k && b[i] != b[k + 1]) k = nxt[k];
		if(b[i] == b[k + 1]) ++k;
		nxt[i] = k;
	}
	int k = 0, tag = 0;
	F(i, 1, n){
		if(k == m) k = nxt[k];
		while(k && a[i] != b[k + 1]) k = nxt[k];
		if(a[i] == b[k + 1]) ++k;
		if(k == m) cout << i - m + 1 << '\n', tag = 1; 
	}
	if(!tag) cout << "NO\n";
	return 0;
}

推荐习题:

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

参考博客:

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

clock: 默认单位是毫秒。

BSGS

求最小正整数 \(x\),使 \(a ^ x \equiv b \pmod p\)\(a, b, p\) 给定,范围在 \(int\) 以内。

\(x = i\times t + j\),我们取 \(t = \sqrt{mod}\)。移项得:

\[a^{it} = ba^j \]

容易发现 \(i, t, j \le \sqrt{mod}\)

所以我们可以预处理所有数对 \((ba^j, j)\),用哈希表存下来。接着枚举 \(i\),当发现存在 \(a^{it}\) 这个键值时,输出对应的 \(i\times t - j\),可以证明是最小的。

  • 简单证一下:每当 \(i + 1\)\(ans\) 增加 \(t\),而由于 \(j \le t\),所以必然不优。
#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;
}

二分图判定-染色法

原理就是交替染色,如果发现一条边两端点颜色相同就不合法。

不要用什么 bool dfs() 的烂写法!写一次忘一次!

void dfs(int u, int c){
	if(!flag) return ;
	col[u] = c;
	for(int i = first[u]; i; i = e[i].ne){
		int v = e[i].v;
		if(!col[v]) dfs(v, 3 - c);
		else if(col[v] == c) return flag = 0, void();
	}
}
bool check(){
	flag = 1;
	F(i, 1, n) if(!col[i]){
		dfs(i, 1);
		if(!flag) return 0;
	} return 1;
}

二分图最大匹配

理没理解增广路什么的不重要,只要记住遇到已经 \(match\) 了的就不断往后找就行了。

原理是 每次尝试从每个左部出发寻找增广路,最坏情况下每次遍历整张图,共 \(n\)

你理解了原理就知道为什么每轮都要初始化 vis 了。

bool dfs(int u){
	for(auto v : G[u]){
		if(!vis[v]){
			vis[v] = 1;
			if(!match[v] || dfs(match[v])){
				match[v] = u;
				return 1;
			}
		}
	}
	return 0;
}
signed main(){
	F(i, 1, n){
		memset(vis, 0, sizeof(vis)) ;
		if(dfs(i)) ++ ans;
	}
}
posted @ 2024-09-23 08:32  superl61  阅读(11)  评论(0编辑  收藏  举报