五一 DAY 5
五一 DAY 5
V 点 1----n
E 边
/* Given a graph with N nodes and M unidirectional edges. Each edge e_i starts from u_i to v_i and weights w_i Output a travelsal from node 1 and output degree of each node. 给出了一个具有n个节点和m个单向边的图。 每边E_i从U_i开始到V_i,重量W_i 从节点1输出一个航迹,输出每个节点的度数。 */ #include <bits/stdc++.h> using namespace std; const int N = 5005; int ideg[N], odeg[N], n, m, edg[N][N]; bool visited[N]; void travel(int u, int distance) { cout << u << " " << distance << endl; visited[u] = true; for (int v = 1; v <= n; v++) if (edg[u][v] != -1 && !visited[v]) travel(v, distance + edg[u][v]); //if there is an edge (u, v) and v has not been visited, then travel(v) } int main() { cin >> n >> m; memset(edg, -1, sizeof edg); //没有边 memset(visited, false, sizeof visited); //没访问 for (int u, v, w, i = 1; i <= m; i++) cin >> u >> v >> w, edg[u][v] = w, odeg[u]++, ideg[v]++; //u出度,V入度 for (int i = 1; i <= n; i++) cout << ideg[i] << " " << odeg[i] << endl; for (int i = 1; i <= n; i++) if (!visited[i]) travel(i, 0); }
数组版本:
/* Given a graph with N nodes and M unidirectional edges. Each edge e_i starts from u_i to v_i and weights w_i Output a travelsal from node 1 and output degree of each node. 给出了一个具有n个节点和m个单向边的图。 每边E_i从U_i开始到V_i,重量W_i 从节点1输出一个航图,输出每个节点的度数。 */ #include <bits/stdc++.h> using namespace std; const int N = 5005; struct edge { int u, v, w; edge *next; edge(int _u, int _v, int _w, edge *_next): u(_u), v(_v), w(_w), next(_next) {} }; edge *head[N]; //List[u] stores all edges start from u 最前面节点 int ideg[N], odeg[N], n, m; bool visited[N]; void add(int u, int v, int w) { edge *e = new edge(u, v, w, head[u]); head[u] = e; } void travel(int u, int distance) { cout << u << " " << distance << endl; visited[u] = true; for (edge *e = head[u]; e ; e = e -> next) if (!visited[e -> v]) travel(e -> v, distance + e -> w); //if there is an edge (u, v) and v has not been visited, then travel(v) } int main() { cin >> n >> m; memset(visited, false, sizeof visited); memset(head, 0, sizeof head); for (int u, v, w, i = 1; i <= m; i++) cin >> u >> v >> w, add(u, v, w), odeg[u]++, ideg[v]++; for (int i = 1; i <= n; i++) cout << ideg[i] << " " << odeg[i] << endl; for (int i = 1; i <= n; i++) if (!visited[i]) travel(i, 0); }
指针版本:
Head : 边的编号
#include <bits/stdc++.h> using namespace std; const int N = 5005; struct edge { int u, v, w, next; }edg[N]; int head[N]; //List[u] stores all edges start from u int ideg[N], odeg[N], n, m, cnt; //cnt: numbers of edges bool visited[N]; void add(int u, int v, int w) { int e = ++cnt; edg[e] = (edge){u, v, w, head[u]}; head[u] = e; } void travel(int u, int distance) { cout << u << " " << distance << endl; visited[u] = true; for (int e = head[u]; e ; e = edg[e].next) if (!visited[edg[e].v]) travel(edg[e].v, distance + edg[e].w); //if there is an edge (u, v) and v has not been visited, then travel(v) } int main() { cin >> n >> m; cnt = 0; memset(visited, false, sizeof visited); memset(head, 0, sizeof head); for (int u, v, w, i = 1; i <= m; i++) cin >> u >> v >> w, add(u, v, w), odeg[u]++, ideg[v]++; for (int i = 1; i <= n; i++) cout << ideg[i] << " " << odeg[i] << endl; for (int i = 1; i <= n; i++) if (!visited[i]) travel(i, 0); } /* Given a graph with N nodes and M unidirectional edges. Each edge e_i starts from u_i to v_i and weights w_i Output a travelsal from node 1 and output degree of each node. */
和传统二维数组相比,可以防止浪费,用多少开多少
#include <bits/stdc++.h> using namespace std; const int N = 5005; struct edge { int u, v, w; }; //vector<edge> edg; //edg是变长数组 vector<edge> edg[N]; //n个变长数组 int ideg[N], odeg[N], n, m, cnt; //cnt: numbers of edges bool visited[N]; void add(int u, int v, int w) { edg[u].push_back((edge){u, v, w});//(edge){u, v, w}强制类型转化 } void travel(int u, int distance) { cout << u << " " << distance << endl; visited[u] = true; for (int e = 0; e < edg[u].size(); e++) if (!visited[edg[u][e].v]) //以u出发的第e条出边 travel(edg[u][e].v, distance + edg[u][e].w); //if there is an edge (u, v) and v has not been visited, then travel(v) } int main() { cin >> n >> m; cnt = 0; memset(visited, false, sizeof visited); for (int u, v, w, i = 1; i <= m; i++) cin >> u >> v >> w, add(u, v, w), odeg[u]++, ideg[v]++; for (int i = 1; i <= n; i++) cout << ideg[i] << " " << odeg[i] << endl; for (int i = 1; i <= n; i++) if (!visited[i]) travel(i, 0); } /* Given a graph with N nodes and M unidirectional edges. Each edge e_i starts from u_i to v_i and weights w_i Output a travelsal from node 1 and output degree of each node. */
MST问题
也就是保留点,删除边(不一定每个点都要留下)
生成树不唯一,数量是指数级别
蓝色的边和点构成一个生成树
红色的边和点构成一个生成树
显然红色的树更合题意
所以此处可以用到并查集
最常用
把边拿掉,一点一点加进去
判断是否连通,并查集
为什么克鲁斯卡尔是对的
反正最后还是要吧a,v,连起来,早连接,权值小
严谨证明:
消圈算法(麻烦,不用)
假设原图有个圈
不断断开权值最大边
剩下的就是最小生成树
#include <bits/stdc++.h> using namespace std; const int maxn = 1000005; struct edge { int u, v, w; }edg[maxn]; int n, m, p[maxn], ans = 0; bool cmp(edge a, edge b) //小到大 {return a.w < b.w;} int findp(int t) {return p[t] ? p[t] = findp(p[t]) : t;} bool merge(int u, int v) { u = findp(u); v = findp(v); if (u == v) return false; p[u] = v; return true; } int main() { cin >> n >> m; for (int i = 1, u, v, w; i <= m; i++) cin >> u >> v >> w, edg[i] = (edge){u, v, w}; sort(edg + 1, edg + m + 1, cmp); for (int i = 1; i <= m; i++) if (merge(edg[i].u, edg[i].v)) //并茶几 ans = max(ans, edg[i]. w); cout << ans << endl; }
Prim
有些麻烦
先选择一号点,所有出边中最小的,联通,
然后找距离连通块最近的点,联通
总结: 先选择一号点,找出与当前联通快最小的边
(一个联通快,不断扩大)
Kosaraju
N个连通块,不断合并
第一轮每个连通块找到与他相连的最小边
(用堆优化)
给出一张图
简单路径:不经过重复的点
鬼畜路径:可以转圈圈
路径的长度:
路径中所有边,边权值和
SSP
用中心点更新最短路
#include <bits/stdc++.h> using namespace std; const int N = 505; const int inf = 1 << 29; int d[N][N], n, m; //邻接矩阵存图 int main() { cin >> n >> m; for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) { if(i==j) d[i][j]=0; //d[i][i]应该为0 ,我到我自己 else d[i][j]=inf; } //初始化无穷大 for (int u, v, w, i = 1; i <= m; i++) cin >> u >> v >> w, d[u][v] = min(d[u][v], w); //处理重边 for (int k = 1; k <= n; k++) for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) d[i][j] = min(d[i][j], d[i][k] + d[k][j]); //必须先枚举中间点 //可以求出任意两点间的最短路径 }
一般来说题目只能做n<=500
结束后枚举d[u][u]如果有负数,证明有负权环
Floyd不可以处理负权环,但是可以判定有无
[代码]:
#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 5; const int inf = 1 << 29; struct edge{ int u, v, w; }edg[N]; int d[N], n, m, S; int main() { cin >> n >> m >> S; for (int i = 1; i <= n; i++) d[i] = inf; for (int u, v, w, i = 1; i <= m; i++) cin >> u >> v >> w, edg[i] = (edge){u, v, w}; d[S] = 0; for (int i = 1; i <= n; i++) //n点 for (int j = 1; j <= m; j++) //枚举m条边 { int u = edg[j].u, v = edg[j].v, w = edg[j].w; d[v] = min(d[v], d[u] + w); } }
其实是bellman-ford的队列优化版
对比bellman-ford
一共n个点,全局松弛,把所有边都松弛一遍
假设点1可以更新点3,点5,
第一次全局更新的时候没有把2,4 更新的更小,下一轮也不用更新他了,否则就是重复计算
由于3,5被更新的更小,所以他们的出边继续更新才有机会把下面的点最短路径更新的更小
但是Spfa 会被网格图卡的很慢
#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 5; const int inf = 1 << 29; struct edge{ //邻接表 int u, v, w; }; vector<edge> edg[N]; int d[N], n, m, S; //d是答案 queue<int> Queue; bool inQueue[N]; int cntQueue[N]; void add(int u, int v, int w) { edg[u].push_back((edge){u, v, w}); } int main() { cin >> n >> m >> S; for (int i = 1; i <= n; i++) d[i] = inf; for (int u, v, w, i = 1; i <= m; i++) cin >> u >> v >> w, add(u, v, w); d[S] = 0; inQueue[S] = true; Queue.push(S); //放进s点 while (!Queue.empty()) { int u = Queue.front(); Queue.pop(); inQueue[u] = false; for (int e = 0; e < edg[u].size(); e++) { int v = edg[u][e].v, w = edg[u][e].w; if (d[v] > d[u] + w) { d[v] = d[u] + w; if (!inQueue[v]) { Queue.push(v); ++cntQueue[v]; inQueue[v] = true; if (cntQueue[v] >= n) {cout << "Negative Ring" << endl; return 0;} //发现负权环 } } } } for (int i = 1; i <= n; i++) cout << d[i] << endl; }
用起点松弛其余点,假设点 1 是距离S最近的点,找到这个点之后就用它再来更新下面的点
then,不管S和点1 ,找到d最小的点2 更新,它的 d是真实值
因为它已经被更新过了,而且外边的点的边权都比它大
[代码]:
#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 5; const int inf = 1 << 29; struct edge{ int u, v, w; }; vector<edge> edg[N]; int d[N], n, m, S; bool relaxed[N]; //表示一个元素是否在队列 /*struct Qnode { int u, du; bool operator<(const Qnode &v) const {return v.du < du;} }; priority_queue<Qnode> PQueue; */ void add(int u, int v, int w) { edg[u].push_back((edge){u, v, w}); } int main() { cin >> n >> m >> S; for (int i = 1; i <= n; i++) d[i] = inf; for (int u, v, w, i = 1; i <= m; i++) cin >> u >> v >> w, add(u, v, w); d[S] = 0; for (int i = 1; i <= n; i++) { int u = 1; while (relaxed[u]) ++u; for (int j = 1; j <= n; j++) if (!relaxed[j] && d[j] < d[u]) u = j; //find a node u not relaxed yet with smallest d(u) //寻找第一个不在队列里的 d最小的u relaxed[u] = true; for (int e = 0; e < edg[u].size(); e++) { int v = edg[u][e].v, w = edg[u][e].w; d[v] = min(d[v], d[u] + w); } } for (int i = 1; i <= n; i++) cout << d[i] << endl; }
[堆优化Dijkstra]:
//加优化 #include <bits/stdc++.h> using namespace std; const int N = 1e5 + 5; const int inf = 1 << 29; struct edge{ int u, v, w; }; vector<edge> edg[N]; int d[N], n, m, S; bool relaxed[N]; struct Qnode { //堆里元素 int u, du; bool operator<(const Qnode &v) //const不能少 const {return v.du < du;} //小的在队顶 }; priority_queue<Qnode> PQueue; //优先队列 void add(int u, int v, int w) { edg[u].push_back((edge){u, v, w}); } int main() { cin >> n >> m >> S; for (int i = 1; i <= n; i++) d[i] = inf; for (int u, v, w, i = 1; i <= m; i++) cin >> u >> v >> w, add(u, v, w); d[S] = 0; PQueue.push((Qnode){S, 0}); //需要更新的点 while (!PQueue.empty()) { int u = PQueue.top().u; PQueue.pop(); //对不为空 if (relaxed[u]) continue; //松弛过就不松 //if edges staring from u are already relaxed, no need to relax again. relaxed[u] = true; //打标机 for (int e = 0; e < edg[u].size(); e++) //枚举出边 { int v = edg[u][e].v, w = edg[u][e].w; //u v 是w的出边 if (d[v] > d[u] + w) { d[v] = d[u] + w; PQueue.push((Qnode){v, d[v]}); //if d(v) is updated, push v into PQueue } } } for (int i = 1; i <= n; i++) cout << d[i] << endl; }
将有向图的所有的有向边替换为无向边,所得到的图称为原图的基图。如果一个有向图的基图是连通图,则有向图是弱连通图。
找拓扑序的方法:
1.找出度为0 的点,放在拓扑序列最后面,指向该点的边删掉,与所删除的边起点出度数-1
2.重复以上操作,得出拓扑序
结论1 有拓扑序一定是DAG
结论2 任意DAG一定有拓扑序
任意DAG一定有出度为0的点,否则有环,
出度为0的点放在拓扑序最后
把这个点和他的入度边删去,图还是个DAG
同上操作,做完后就是拓扑序
#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 5; const int inf = 1 << 29; struct edge{ int u, v; }; vector<edge> edg[N]; int n, m, outdeg[N], ans[N]; queue<int> Queue; //出度为0 void add(int u, int v) { edg[u].push_back((edge){u, v}); } int main() { cin >> n >> m; for (int u, v, i = 1; i <= m; i++) cin >> u >> v, add(v, u), outdeg[u]++; //找入度,倒着记录 for (int i = 1; i <= n; i++) if (outdeg[i] == 0) Queue.push(i); for (int i = 1; i <= n; i++) { if (Queue.empty()) {printf("Not DAG"); return 0;} int u = Queue.front(); Queue.pop(); ans[n - i + 1] = u; //倒着 for (int e = 0; e < edg[u].size(); e++) //指向u的边删掉 { int v = edg[u][e].v; //指向u的点 出度减少 if (--outdeg[v] == 0) //先自减,再使用 Queue.push(v); } } }
pro1
考虑DAG上找单元最短路
1到不了3
1松弛出边7 5 出边肯定在自己后边
然后出边在更新自己出边,直达完成
自己被更新完就不能被改变了,他要继续更新自己出边
pro 2
考虑DAG求拓扑排序数量
(做不到)
LCA(写在这里啦)
ans=len(x)-len(t)+len(y)+len(t)
=len(x)+len(y)-2*len(t)