自用的板子(搜索与图论)
一、DFS和BFS
1、DFS:N皇后问题、树的重心
2、BFS:迷宫问题、图中点的层次
二、拓扑排序
用于解决有向图,比如某个条件是解决另外一个条件的先前条件
时间复杂度:O(V + E)
链式前向星建有向图,入点++然后跑拓扑
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int h[N], e[N], ne[N], idx;
int q[N], d[N];
int n, m;
void add(int a, int b){e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
bool toposort(){int hh = 0, tt = -1;for(int i = 1; i <= n; i++){if(!d[i])q[++tt] = i;}while(hh <= tt){int t = q[hh++];for(int i = h[t]; i != -1; i = ne[i]){int j = e[i];d[j]--;if(!d[j])q[++tt] = j;}}return tt == n - 1;
}
signed main(){ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);cin >> n >> m;memset(h, -1, sizeof(h));while(m--){int a, b; cin >> a >> b;add(a, b);d[b]++;}if(toposort()){for(int i = 0; i < n; i++)cout << q[i] << " ";}else cout << "-1" << endl;return 0;
}
三、迪杰斯特拉算法(Dijkstra)----不存在负边权
形式一:邻接矩阵存图(稠密图),时间复杂度O(n^2)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 510;
int n, m;
int g[N][N];
int dist[N];
int st[N];
int dijkstra(){memset(dist, 0x3f, sizeof(dist));dist[1] = 0;for(int i = 0; i < n; i++){int t = -1;for(int j = 1; j <= n; j++){if(!st[j] && (t == -1 || dist[t] > dist[j]))t = j;}if(t == n)break;st[t] = true;for(int j = 1; j <= n; j++){dist[j] = min(dist[j], dist[t] + g[t][j]);}}if(dist[n] == 0x3f3f3f3f)return -1;return dist[n];
}signed main(){ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);cin >> n >> m;memset(g, 0x3f, sizeof(g));while(m--){int a, b, c; cin >> a >> b >> c;g[a][b] = min(g[a][b], c);}int t = dijkstra();cout << t << endl;return 0;
}
形式二:堆优化的迪杰斯特拉,邻接表存图(稀疏图),时间复杂度O((m+n)logn)
代码:
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 150010;
int n, m;
int h[N], e[N], ne[N], idx, w[N];
int dist[N];
bool st[N];
void add(int a, int b, int c){e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int dijkstra(){memset(dist, 0x3f, sizeof(dist));dist[1] = 0;priority_queue<PII, vector<PII>, greater<PII>>heap;heap.push({0, 1});while(heap.size()){auto t = heap.top();heap.pop();int ver = t.second, distance = t.first;if(st[ver])continue;st[ver] = true;for(int i = h[ver]; i != -1; i = ne[i]){int j = e[i];if(dist[j] > distance + w[i]){dist[j] = distance + w[i];heap.push({dist[j], j});}}}if(dist[n] == 0x3f3f3f3f)return -1;else return dist[n];
}signed main() {ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);cin >> n >> m;memset(h, -1, sizeof(h));while(m--){int a, b, c; cin >> a >> b >> c;add(a, b, c);}int t = dijkstra();cout << t << endl;return 0;
}
四、贝尔曼福特算法(Bellman-ford)---解决负边权
思路:for循环n次,备份(for)所有边a,b,w表示所有a到b的边权值为w,(松弛),处理有福边权,能判断是否有负环(但是一般用spfa做)。第k次(k是步数限制)松弛:找到经过最多k条边的最短路径,通过多次松弛,算法可以逐步覆盖从起点到所有节点的所有可能路径。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 510, M = 10010;
int n, m, k;
int dist[N], backup[N];
struct Edge{int a, b, w;
}edges[M];
int bellman_ford(){memset(dist, 0x3f, sizeof(dist));dist[1] = 0;for(int i = 0; i < k; i++){memcpy(backup, dist, sizeof(dist));for(int j = 0; j < m; j++){int a = edges[j].a, b = edges[j].b, w = edges[j].w;dist[b] = min(dist[b], backup[a] + w);}}if(dist[n] > 0x3f3f3f3f / 2)return -0x3f3f3f3f;return dist[n];
}
signed main(){ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);cin >> n >> m >> k;for(int i = 0; i < m; i++){cin >> edges[i].a >> edges[i].b >> edges[i].w;}int t = bellman_ford();if(t == -0x3f3f3f3f)cout << "impossible" << endl;else cout << t << endl;return 0;
}
五、SPFA算法
1、求最短路----带负边权
用队列,只要有变小就取出队头,更新t的所有出边,成功就加入队列(更新过谁就那谁出来操作)通过队列优化Bellman-Ford算法,只对距离发生变化的节点进行松弛操作
时间复杂度:最好情况O(m),最坏情况O(nm)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, m;
int h[N], e[N], ne[N], idx, w[N];
int dist[N];
bool st[N];
void add(int a, int b, int c){e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int spfa(){memset(dist, 0x3f, sizeof(dist));dist[1] = 0;queue<int> q;q.push(1);st[1] = true;while(q.size()){auto t = q.front();q.pop();st[t] = false;for(int i = h[t]; i != -1; i = ne[i]){int j = e[i];if(dist[j] > dist[t] + w[i]){dist[j] = dist[t] + w[i];if(!st[j]){st[j] = true;q.push(j);}}}}if(dist[n] == 0x3f3f3f3f)return -0x3f3f3f3f;return dist[n];
}
signed main(){ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);cin >> n >> m;memset(h, -1, sizeof(h));while(m--){int a, b, c; cin >> a >> b >> c;add(a, b, c);}int t = spfa();if(t == -0x3f3f3f3f)cout << "impossible" << endl;else cout << t << endl;return 0;
}
2、判负环
思路:维护多一个cnt[x]=cnt[t] + 1的数组,如果cnt[x] >= n(本来是n-1条边)(处理的点多)说明存在负环,先全部点进队,再跑spfa,dist数组不用初始化。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 2010, M = 10010;
int h[N], e[M], ne[M], idx, w[M];
int dist[N], cnt[N];
bool st[N];
int n, m;
void add(int a, int b, int c){e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
bool spfa(){queue<int> q;for(int i = 1; i <= n; i++){q.push(i);st[i] = true;}while(q.size()){int t = q.front();q.pop();st[t] = false;for(int i = h[t]; i != -1; i = ne[i]){int j = e[i];if(dist[j] > dist[t] + w[i]){dist[j] = dist[t] + w[i];cnt[j] = cnt[t] + 1;if(cnt[j] >= n)return true;if(!st[j]){q.push(j);st[j] = true;}}}}return false;
}signed main(){ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);cin >> n >> m;memset(h, -1, sizeof(h));while(m--){int a, b, c; cin >> a >> b >> c;add(a, b, c);}if(spfa())cout << "Yes" << endl;else cout << "No" << endl;return 0;
}
六、弗洛伊德Floyd算法
用于处理多源最短路(支持负权,不支持负环)可以检测负环
思路:逐步考虑每个节点作为中间节点,更新所有节点对之间的最短距离
时间复杂度:O(n^3)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 210, INF = 1e9;
int n, m, q;
int d[N][N];
void floyd(){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]);}}}
}
signed main(){ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);cin >> n >> m >> q;for(int i = 1; i <= n; i++){for(int j = 1; j <= n; j++){if(i == j)d[i][j] = 0;else d[i][j] = INF;}}while(m--){int a, b, w; cin >> a >> b >> w;d[a][b] = min(d[a][b], w);}floyd();while(q--){int a, b; cin >> a >> b;if(d[a][b] > INF / 2)cout << "impossible" << endl;else cout << d[a][b] << endl;}return 0;
}
七、普里姆Prim算法
1、邻接矩阵--稠密图
思路:dist[j]是j点到最小生成树的最小距离,找未加入最小生成树的最小的节点,(找到的距离INF说明不连通),找到后加入权重总值res,再更新其他节点到生成树的最小距离
建图:邻接矩阵g[a][b] = g[b][a] = min(g[a][b], c)
时间复杂度:O(V^2)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 520, INF = 0x3f3f3f3f;
int n, m;
int g[N][N];
int dist[N];
bool st[N];
int prim(){memset(dist, 0x3f, sizeof(dist));dist[1] = 0;int res = 0;for(int i = 0; i < n; i++){int t = -1;for(int j = 1; j <= n; j++){if(!st[j] && (t == -1 || dist[t] > dist[j]))t = j;}if(i && dist[t] == INF)return INF;if(i)res += dist[t];for(int j = 1; j <= n; j++)dist[j] = min(dist[j], g[t][j]);st[t] = true;}return res;
}
signed main(){ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);cin >> n >> m;memset(g, 0x3f, sizeof(g));while(m--){int a, b, c; cin >> a >> b >> c;g[a][b] = g[b][a] = min(g[a][b], c);}int t = prim();if(t == INF)cout << "impossible" << endl;else cout << t << endl;return 0;
}
2、二叉堆 --稀疏图
时间复杂度:O(ElogV)
八、克鲁斯卡尔Kruskal算法
思路:结构体数组对权值排序,res存生成树总权值,cnt存边数,不在统一个集合就合并,加权值,加边数,判断cnt是否<n-1是的话就不连通,可以的话输出最小生成树总权值即可。理论上是从小到大选边
时间复杂度:O(ElogE)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, m;
int p[N];
struct Edge{int a, b, w;bool operator<(const Edge&W)const{return w < W.w;}
}edges[N];
int find(int x){return x == p[x] ? x : p[x] = find(p[x]);
}signed main(){ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);cin >> n >> m;for(int i = 0; i < m; i++){int a, b, w; cin >> a >> b >> w;edges[i] = {a, b, w};}sort(edges, edges + m);for(int i = 1; i <= n; i++)p[i] = i;int res = 0, cnt = 0;for(int i = 0; i < m; i++){int a = edges[i].a, b = edges[i].b, w = edges[i].w;a = find(a), b = find(b);if(a != b){p[a] = b;res += w;cnt++;}}if(cnt < n - 1)cout << "impossible" << endl;else cout << res << endl;return 0;
}
九、染色法
用于判断二分图
十、匈牙利算法
用于解决二分图的最大匹配