基础模板清理
筛法求素数
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
}
}
}
参考博客:
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 ( 题解 )
扩展欧几里得算法
\(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}\)。移项得:
容易发现 \(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;
}
}