图论学习笔记
BFS
BFS的本质是将点分成两个集合,还没有访问的集合Open和已经访问的集合Closed。
从每个点指向它BFS遍历到的点,产生一颗BFS树。
BFS可以用来求最小环,考虑BFS是一层一层遍历的,最小环可以和Floyd一样从某条边裂开,这样就变成了两段距离的和最小,BFS按层次遍历可以保证这一点,当遇到一个之前已经访问过的节点的时候,就出现了最小环,BFS树可以直观体现这一事实。
插入一个牛逼题:CF1325E
某个数为\(\prod_{i = 1}^{n}p_i^{k_i}\),则因子个数为\(\prod_{i = 1}^{n}(k_i + 1)\)。
那么如果有3个质因子,就有8个因子了,所以一个数最多有2个质因子。
如果一个数的质因子的幂是偶数,那么它对答案没有贡献,所以只计算它的幂次是奇数的质因子有哪些。
那么就两种情况:
有两个p和q,则p到q连一条无向边。
只有一个p,则1到p连一条无向边。
之后在这个无向图上求最小环,非常的妙,因为选中一条边实际上就是选择一个数,环上的每个质因子都出现了两次那最后就是一个平方数,然后最小环所以可以保证选择的数的个数最少。
泰酷拉!
因为边权都是1所以直接用BFS找最小环,然后注意到每个数不超过1e6,那么[1,1000]这个区间上肯定有在最后答案中的最小环上的点,从这些点开始BFS就行。
我调了一下午,wa在第151个点二十几次,妈的……
后来发现是BFS找最小环不能找到第一个就break,真小丑了。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
const int N = 1e5;
int prime[N * 2 + 50], head[N * 4 + 50], vis[N * 4 + 50], dis[N * 4 + 50], numb[1000050];
int n, pcnt, num = -1, ans = 0x3f3f3f3f, tcnt, flag, shijianchuo;
struct Node {
int nxt, v;
} edge[N * 4 + 50];
void Addedge(int u, int v) {
// cout << prime[u] << " " << prime[v] << " " << num + 1 << endl;
edge[++num] = (Node){head[u], v};
head[u] = num;
return;
}
bool Prime(int x) {
if (x == 1) return 0;
for (int i = 2; i < x; i++)
if (x % i == 0) return 0;
return 1;
}
void Work(int x) {
int p[5]; p[0] = 0;
for (int i = 1; i <= tcnt && prime[i] <= x; i++)
if (x % prime[i] == 0) {
int sum = 0;
while (x % prime[i] == 0) {
x /= prime[i];
sum++;
}
if (sum & 1) p[++p[0]] = i;
}
if (x > 1 && !numb[x]) numb[x] = ++pcnt; if (x > 1) p[++p[0]] = numb[x];
if (!p[0]) {
puts("1");
exit(0);
}
if (p[0] == 1) Addedge(0, p[1]), Addedge(p[1], 0);
else Addedge(p[1], p[2]), Addedge(p[2], p[1]);
return;
}
void Bfs(int x) {
// cout << "------" << endl;
queue<pair<int, int> >q;
q.push(make_pair(x, -1)); vis[x] = shijianchuo; dis[x] = 0;
while (!q.empty()) {
int u = q.front().first, lst = q.front().second; q.pop(); //cout << prime[u] << endl;
for (int i = head[u]; i != -1; i = edge[i].nxt) {
int v = edge[i].v;
// cout << prime[v] << "*" << " " << i << " " << vis[u] << endl;
if (i == lst || i == (lst ^ 1)) continue;
if (vis[v] == shijianchuo) {
// cout << prime[u] << " " << prime[v] << endl;
ans = min(ans, dis[u] + dis[v] + 1);
}
else {
vis[v] = shijianchuo;
dis[v] = dis[u] + 1;
q.push(make_pair(v, i));
}
}
}
return;
}
int main() {
prime[0] = 1;
for (int i = 2; i <= 1000; i++)
if (Prime(i))
prime[++pcnt] = i, numb[prime[i]] = pcnt;
tcnt = pcnt;
scanf("%d", &n);
memset(head, -1, sizeof(head));
for (int i = 1, x; i <= n; i++) scanf("%d", &x), Work(x);
shijianchuo = 5;
for (int i = 0; i <= 1000; i++) {
flag = 0; shijianchuo++;
Bfs(i);
}
printf("%d", (ans == 0x3f3f3f3f ? -1 : ans));
return 0;
}
双端队列BFS
思考BFS求最短路为什么是对的。
BFS求最短路实际上是保证了在队列中的所有点是按从前往后的顺序最短路依次递增的,这样对于只有边权为0和1的图,可以用双端队列前面放0后面放1来保证正确性。
优先队列BFS
那这样是不是就能尝试用优先队列去维护这个递增性,靠,Dijkstra!
最短路
求从u到v的经过奇数\偶数条边的最短路,只需要对每个点拆成(u, 0)和(u, 1),每次连边的时候让奇偶性改变,(u, v)拆成(u, 0)->(v, 1)和(u, 1)->(v, 0)。
求一条边(u, v)在不在a和b最短路上\(dis_{a, u} + (u, v) + dis_{v, b} = dis_{a, b}\)
求一个点u在不在a和b最短路上\(dis_{a, u} + dis_{u, b} = dis_{a, b}\)
Bellman-Ford && SPFA
太困了,先咕咕咕!
Bellman-Ford可以看做松弛操作的BFS。
最短路上最多有n-1个结点,所以当一条最短路上有n个结点则为负环。
从一个点找不到负环可能是走不到负环,所以可以建超级源点向所有点连边权为0的边,然后从超级源点开始判断。
Floyd
Floyd可以用来找最小环,考虑最小环上一定有一个结点的编号最大,枚举\(dis_{k - 1, x, y} + (y, k) + (k, x)\)。
Floyd还可以用来搞传递闭包,用bitset优化。
Dijkstra
大体意思是把所有点分成两个集合S和T,S里面存有所有找到最短路的点,T里面是还没有确定最短路的点,每次在T中找一个最短的扔进S,再拿它去松弛所有T里面的点。
然后具体优化的时候是优化从T中贪心找出最短的那个点这个过程,可以使用优先队列。
树
若树上所有边边权均为正,则树的所有直径中点重合。