文章目录



DFS

排列数字

#include<iostream>

using namespace std;

const int N = 10;

int n;
int path[N];

void dfs(int u, int state){

if(u == n){

for(int i = 0; i < n; i++) printf("%d ", path[i]);
puts("");
return;
}

for(int i = 0; i < n; i++){

if(state >> i & 1) continue;

path[u] = i + 1;
dfs(u + 1, state | (1 << i));
}
}

int main(){

scanf("%d", &n);

dfs(0, 0);

return 0;
}

n 皇后问题

用截距表示一条斜线
正对角线:
3)搜索与图论_#include
3)搜索与图论_ios_02(由于存在 3)搜索与图论_ios_03),所以加上一个整数 3)搜索与图论_搜索与图论_04
3)搜索与图论_搜索与图论_05
负对角线:
3)搜索与图论_#include_06
3)搜索与图论_ios_07

​解法一​

#include<iostream>

using namespace std;

const int N = 20;

int n;
char g[N][N];
bool col[N], dg[N], udg[N];


void dfs(int u){

if(u == n){

for(int i = 0; i < n; i++) puts(g[i]);
puts("");
return;
}

for(int i = 0; i < n; i++){

if(!col[i] && !dg[u + i] && !udg[n - u + i]){

g[u][i] = 'Q';
col[i] = dg[u + i] = udg[n - u + i] = true;
dfs(u + 1);
col[i] = dg[u + i] = udg[n - u + i] = false;
g[u][i] = '.';
}
}
}


int main(){

cin >> n;
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
g[i][j] = '.';

dfs(0);

return 0;
}

​解法二​

#include<iostream>

using namespace std;

const int N = 10;

int n;
char g[N][N];
// 列; 正对角线;副对角线
bool row[N], col[N], dg[N << 1], udg[N << 1];

void dfs(int x, int y, int s){

if(s > n) return;
if(y == n) y = 0, x++;

if(x == n){

if(s == n){

for(int i = 0; i < n; i++) puts(g[i]);
puts("");
}

return;
}

g[x][y] = '.';
dfs(x, y + 1, s); // 不放

// 放
if(!row[x] && !col[y] && !dg[x + y] && !udg[x - y + n]){

row[x] = col[y] = dg[x + y] = udg[x - y + n] = true;
g[x][y] = 'Q';
dfs(x, y + 1, s + 1);
g[x][y] = '.';
row[x] = col[y] = dg[x + y] = udg[x - y + n] = false ;
}
}

int main(){

cin >> n;

for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
g[i][j] = '.';
}
}

dfs(0, 0, 0);

return 0;
}

BFS

走迷宫

#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

typedef pair<int, int> PII;

const int N = 110;

int n, m;
int g[N][N], d[N][N];
PII q[N * N];

int bfs(){

memset(d, -1, sizeof(d));
d[0][0] = 0;

int hh = 0, tt = 0;
q[0] = {0, 0};

int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

while(hh <= tt){

auto t = q[hh++];

for(int i = 0; i < 4; i++){

int x = t.first + dx[i], y = t.second + dy[i];

if(x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1){

d[x][y] = d[t.first][t.second] + 1;
q[++tt] = {x, y};
}
}
}

return d[n - 1][m - 1];
}

int main(){

cin >> n >> m;

for(int i = 0; i < n; i++){

for(int j = 0; j < m; j++){

cin >> g[i][j];
}
}

cout << bfs() << endl;

return 0;
}

八数码

#include<iostream>
#include<algorithm>
#include<unordered_map>
#include<queue>

using namespace std;

int bfs(string state){

queue<string> q;
unordered_map<string, int> d;

q.push(state);
d[state] = 0;

int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

string end = "12345678x";

while(q.size()){

auto t = q.front();
q.pop();

if(t == end) return d[t];

int distance = d[t];
int k = t.find('x');
int x = k / 3, y = k % 3;
for(int i = 0; i < 4; i++){

int a = x + dx[i], b = y + dy[i];
if(a >= 0 && a < 3 && b >= 0 && b < 3){

swap(t[a * 3 + b], t[k]);
if(!d.count(t)){
d[t] = distance + 1;
q.push(t);
}
swap(t[a * 3 + b], t[k]);
}
}
}

return -1;
}

int main(){

char s[2];

string state;
for(int i = 0; i < 9; i++){

cin >> s;
state += *s;
}

cout << bfs(state) << endl;

return 0;
}

树与图的深度优先遍历

树的重心

/*
给定一颗树,树中包含 n 个结点(编号 1∼n)和 n−1 条无向边。
请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。
重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。
*/

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 100010, M = N << 1;

int n;
int h[N], e[M], ne[M], idx;
int ans = N;
bool st[N];

void add(int a, int b){

e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

int dfs(int u){

st[u] = true;

int sum = 0, size = 0;

for(int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
if(!st[j]){

int s = dfs(j);
// 求得每个分支数的最大值
size = max(size, s);
// 子树的总节点数
sum += s;
}
}

// 求分支最大值和(除(当前以u为子树)的其余节点(即删除当前节点后的上面的部分))的最大值
// 即得到的是删除当前节点的其他连通块中最大的值
size = max(size, n - sum - 1);

// 求删除某节点后,(连通块值最大)的最小
// 找到重心,并得到删除重心后,连通块值最大值
ans = min(ans, size);

return sum + 1;
}

int main(){

scanf("%d", &n);

memset(h, -1, sizeof(h));

for(int i = 0; i < n - 1; i++){

int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}

dfs(1);

printf("%d\n", ans);

return 0;
}

树与图的广度优先遍历

图中点的层次

// 图的存储(邻接表)
// 请你求出 1 号点到 n 号点的最短距离,不能到达则输出-1
#include<iostream>
#include<cstring>

using namespace std;

const int N = 100010;

int n, m;
int h[N], e[N], ne[N], idx;
int d[N], q[N];

void add(int a, int b){

e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

int bfs(){

memset(d, -1, sizeof d);
int hh = 0, tt = 0;
d[1] = 0;
q[0] = 1;

while(hh <= tt){

int t = q[hh++];

for(int i = h[t]; ~i; i = ne[i]){

int j = e[i];
if(d[j] == -1){

d[j] = d[t] + 1;
q[++tt] = j;
}
}
}


return d[n];
}

int main(){

scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);

for(int i = 0; i < m; i++){

int a, b;
scanf("%d%d", &a, &b);
add(a, b);
}

cout << bfs() << endl;

return 0;
}

拓扑排序

有向图的拓扑序列

#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 100010;
int n, m;
// 邻接表
int h[N], e[N], ne[N], idx;
int d[N]; // 入度
int q[N]; // 队列

void add(int a, int b){

e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

// 拓扑排序
bool topsort(){

int hh = 0, tt = -1;

// 入度为0的加入队列
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] == 0){

q[++tt] = j;
}
}
}

return tt == n - 1;
}


int main(){

scanf("%d%d", &n, &m);

memset(h, -1, sizeof(h));

for(int i = 0; i < m; i++){

int a, b;

scanf("%d%d", &a, &b);

add(a, b);
// 入度++
d[b]++;
}

if(!topsort()) puts("-1");
else{

for(int i = 0; i < n; i++){

printf("%d ", q[i]);
}

puts("");
}

return 0;
}

最短路算法

3)搜索与图论_ios_08

朴素版dijkstra 算法

3)搜索与图论_i++_09

#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 510, INF = 0x3f3f3f3f;

int n, m;
int g[N][N]; // 邻接矩阵
int dist[N]; // dist数组
bool st[N]; // 标记是否访问过

int dijkstra(){

memset(dist, 0x3f, sizeof(dist));

dist[1] = 0;

for(int i = 0; i < n - 1; i++){

int t = -1;
for(int j = 1; j <= n; j++){

if(!st[j] && (t == -1 || dist[j] < dist[t])){
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] == INF) return -1;

return dist[n];
}

int main(){

scanf("%d%d", &n, &m);

memset(g, 0x3f, sizeof(g));

while(m--){

int a, b, c;
scanf("%d%d%d", &a, &b, &c);

// 重边时取最小的边
g[a][b] = min(g[a][b], c);
}

printf("%d\n", dijkstra());

return 0;
}

堆优化版 djikstra 算法

#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>

using namespace std;

typedef pair<int, int> PII;

const int N = 1e6 + 10, INF = 0x3f3f3f3f;

int n, m;
int h[N], w[N], e[N], ne[N], idx;
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));

// 小根堆
priority_queue<PII, vector<PII>, greater<PII>> q;
q.push({0, 1});
dist[1] = 0;

while(q.size()){

PII cur = q.top();
q.pop();

int t = cur.second;

if(st[t]) continue;

st[t] = true;

for(int i = h[t]; i != -1; i = ne[i]){
int j = e[i];
if(!st[j]){

if(dist[t] + w[i] < dist[j]){

dist[j] = dist[t] + w[i];
q.push({dist[j], j});
}

}
}

}
if(dist[n] == INF) return -1;
return dist[n];
}

int main(){

scanf("%d%d", &n, &m);

memset(h, -1, sizeof(h));

while(m--){

int a, b, c;
scanf("%d%d%d", &a, &b, &c);

add(a, b, c);
}

cout << dijkstra() << endl;

return 0;
}

bellman-ford 算法

3)搜索与图论_搜索与图论_10

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 510, M = 10010, INF = 0x3f3f3f3f;

struct Edge{

int a, b, c;
}edges[M];

int n, m, k;

int dist[N];
int last[N];

void bellman_ford(){

memset(dist, 0x3f, sizeof(dist));
dist[1] = 0;

// 限制路径上最多有k条边
for(int i = 0; i < k; i++){

// 防止发生串联
memcpy(last, dist, sizeof(dist));

for(int j = 0; j < m; j++){

auto e = edges[j];

dist[e.b] = min(dist[e.b], last[e.a] + e.c);
}
}
}

int main(){

scanf("%d%d%d", &n, &m, &k);

for(int i = 0; i < m; i++){

int a, b, c;
scanf("%d%d%d", &a, &b, &c);

edges[i] = {a, b, c};
}

bellman_ford();

if(dist[n] > INF / 2) puts("impossible");
else printf("%d\n", dist[n]);

return 0;
}

SPFA 算法

最坏情况 3)搜索与图论_搜索与图论_10,一般 3)搜索与图论_ios_12

SPFA 求最短路

#include<iostream>
#include<queue>
#include<cstring>

using namespace std;

const int N = 100010, INF = 0x3f3f3f3f;

int n, m;
int h[N], e[N], ne[N], w[N], idx;
bool st[N]; // 记录当前点是否在队列中
int dist[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);

queue<int> q;
q.push(1);
dist[1] = 0;
st[1] = true;

while(q.size()){

int t = q.front();
q.pop();

st[t] = false;

for(int i = h[t]; ~i; i = ne[i]){

int j = e[i];
if(dist[t] + w[i] < dist[j]){
dist[j] = dist[t] + w[i];
if(!st[j]) {
q.push(j);
st[j] = true;
}
}
}
}

return dist[n];
}

int main(){

scanf("%d%d", &n, &m);

memset(h, -1, sizeof h);

int a, b, c;
for(int i = 0; i < m; i++){

scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}

int t = spfa();

if(t == INF) puts("impossible");
else printf("%d\n", t);

return 0;
}

SPFA 判断负环

#include<iostream>
#include<queue>
#include<cstring>

using namespace std;

const int N = 2010, M = 10010, INF = 0x3f3f3f3f;

int h[N], e[M], ne[M], w[M], idx;
int n, m;
int dist[N], cnt[N]; // cnt记录其他点到当前点经过的边数
bool st[N];

void add(int a, int b, int c){
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

bool spfa(){

// memset(dist, 0x3f, sizeof dist);

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; 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;
}

int main(){

scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);

int a, b, c;
for(int i = 0; i < m; i++){

scanf("%d%d%d", &a, &b, &c);
add(a, b ,c);
}

if(spfa()) puts("Yes");
else puts("No");

return 0;
}

floyd 求最短路

#include<iostream>

using namespace std;

const int N = 210, INF = 0x3f3f3f3f;

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]);
}
}
}
}

int main(){

scanf("%d%d%d", &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;
}
}

int a, b, c;
for(int i = 0; i < m; i++){

scanf("%d%d%d", &a, &b, &c);
d[a][b] = min(d[a][b], c);
}

floyd();

while(Q--){

int a, b;
scanf("%d%d", &a, &b);
if(d[a][b] > INF / 2) puts("impossible");
else printf("%d\n", d[a][b]);
}

return 0;
}

3)搜索与图论_ios_13

最小生成树

Prim 算法求最小生成树

3)搜索与图论_i++_09

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 510, INF = 0x3f3f3f3f;

int n, m;
int g[N][N];
int dist[N];
bool st[N];

int prim(){

memset(dist, 0x3f, sizeof(dist));

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[j] < dist[t])){

t = j;
}
}

// 除了第一个点外,如果找到的点与集合内的点无边时,说明不连通。
if(i && dist[t] == INF) return INF;

st[t] = true;

// 为了防止环的情况自己更新自己, 所以先加
if(i) res += dist[t]; // 集合的第一个点不加dist值

for(int j = 1; j <= n; j++){

dist[j] = min(dist[j], g[t][j]);
}

}

return res;
}

int main(){

scanf("%d%d", &n, &m);

memset(g, 0x3f, sizeof(g));

while(m--){

int a, b, c;
scanf("%d%d%d", &a, &b, &c);
g[a][b] = g[b][a] = min(g[a][b], c);
}

int t = prim();

if(t == INF) puts("impossible");
else printf("%d\n", t);

return 0;
}

kruskal 算法求最小生成树

3)搜索与图论_i++_15

// 对边权进行排序
// 从小到大遍历边选择小的边, 并且满足边的两端点没在一个集合
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 100010, M = 2 * N, INF = 0x3f3f3f3f;

int n, m;
int p[N];

struct Edge{

int a, b, w;
bool operator < (const Edge & e) const{
return w < e.w;
}
}edges[M];

int find(int x){
if(x != p[x]) p[x] = find(p[x]);
return p[x];
}

int kruskal(){

int res = 0, cnt = 0;
for(int i = 0; i < m; i++){

auto t = edges[i];
int a = find(t.a), b = find(t.b);
if(a != b){
p[a] = p[b];
res += t.w;
cnt ++;
}
}
if(cnt != n - 1) return INF;
return res;
}

int main(){

scanf("%d%d", &n, &m);

for(int i = 0; i <= n; i++) p[i] = i;

int a, b, c;
for(int i = 0; i < m; i++){

scanf("%d%d%d", &a, &b, &c);
edges[i] = {a, b, c};
}

sort(edges, edges + m);

int t = kruskal();

if(t == INF) puts("impossible");
else printf("%d\n", t);

return 0;
}

二分图

二分图:当且仅当图中不含奇数环。

将点分为两个集合,集合内部的点没有边。

染色法判断二分图

3)搜索与图论_#include_16

// 二分图,不存在奇数环(边数为奇数的环)
// 无向图 - 双向边
#include<iostream>
#include<cstring>

using namespace std;

const int N = 1e5 + 10, M = 2e5 + 10;

int e[M], ne[M], h[N], idx;
int color[N];
int n, m, a, b;

void add(int a, int b){

e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

bool dfs(int x, int c){

color[x] = c;

for(int i = h[x]; i != -1; i = ne[i]){

int j = e[i];

if(!color[j]){

// 对邻接点进行涂色
if(!dfs(j, 3 - c)) return false;
}

// 如果当前节点与邻接节点涂色相同, 则存在奇数环
if(color[x] == color[j]) return false;
}

return true;
}

int main(){

scanf("%d%d", &n, &m);

memset(h, -1, sizeof(h));

for(int i = 0; i < m; i++){

scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}

bool flag = false;

// 对每一个连通块涂色
for(int i = 1; i <= n; i++){

if(!color[i]){

if(!dfs(i, 1)){

flag = true;
break;
}
}
}

if(flag) puts("No");
else puts("Yes");

return 0;
}

匈牙利算法(二分图的最大匹配)

3)搜索与图论_搜索与图论_10,但实际运行时间一般远小于 3)搜索与图论_搜索与图论_10

/*
给定一个二分图,其中左半部包含 n1 个点(编号 1∼n1),右半部包含 n2 个点(编号 1∼n2),二分图共包含 m 条边。
数据保证任意一条边的两个端点都不可能在同一部分中。
请你求出二分图的最大匹配数。
二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。
二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数
*/

#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 510, M = 100010;

int n1, n2, m;
int h[N], e[M], ne[M], idx;
int match[N]; // n2匹配的对应n1 match[n2] = n1;
bool st[N]; // 一轮匹配中, n2是否已经被匹配了

void add(int a, int b){

e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

// 匹配过程
bool find(int x){

// 遍历男生x的心仪女生
for(int i = h[x]; i != -1; i = ne[i]){

int j = e[i];

// 如果在本轮匹配中, j 女生未被预定
if(!st[j]){

// 标记为true,代表x的候选对象
st[j] = true;

// 如果j在前面的每轮未被匹配过
// 或者匹配过了, 给j的对象能找另一个心仪对象
// 则让x的对象为j(j的对象为x)
if(!match[j] || find(match[j])){

match[j] = x;
return true;
}
}
}

// 如果遍历完心仪对象还未匹配成功,则返回false
return false;
}


int main(){

scanf("%d%d%d", &n1, &n2, &m);

memset(h, -1, sizeof(h));

while(m--){

int a, b;

scanf("%d%d", &a, &b);
add(a, b);
}

int res = 0;
for(int i = 1; i <= n1; i++){

memset(st, false, sizeof(st));
// 匹配成功一次, res++
if(find(i)) res++;
}

printf("%d\n", res);

return 0;
}