基础模板清理

筛法求素数

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\)

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

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;
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\),使 \(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;
}

差分约束系统

不管题目怎么变,认准小于号,然后带入 \(dis_v \le dis_u + w\) 对应各个变量。

“等号”全部处理成 \(a \le b + c\)\(a\ge b + c\) (放缩)。

判负环:\(cnt[v] = cnt[u] + 1\)\(\gt 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\),使得对于 \(\forall 1 \le i \le n\),满足:

\[x \equiv a[i] \pmod {m[i]} \]

设:

\(sum = \prod_{1 \le i \le n} m[i]\)

\(M[i] = sum / m[i]\)

\(t[i] = M[i]^{-1} \pmod{m[i]}\)

则答案为

\[\sum_{1 \le i \le n} a[i] \times t[i] \times M[i] \pmod{sum} \]

#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 @ 2024-09-23 08:32  superl61  阅读(19)  评论(0)    收藏  举报