acwing算法基础课-第三章 搜索与图论

搜索与图论

  • 数与图的存储方式
  • DFS
    • AcWing 842 排列问题
    • AcWing 843 n-皇后问题
  • BFS
    • AcWing 844 走迷宫
    • AcWing 845 八数码
  • 树与图的深度优先遍历
    • 模板
    • AcWing 846 树的重心(模板题)
  • 树与图的广度优先遍历
    • 模板
    • AcWing 847 图中点的层次(模板题)
  • 拓扑排序
    • 模板
    • AcWing 848 有向图的拓扑序列(模板题)
  • 最短路径
  • Dijkstra
    • 模板
    • AcWing 849 Dijkstra 求最短路径 I (模板题(1))
    • AcWing 850 Dijkstra 求最短路 II (模板题(2))
  • bellman_ford
    • 模板
    • AcWnig 853 有边路限制的最短路(模板题)
  • spfa
    • 模板
    • AcWing 851 spfa求最短路 (模板题(1))
    • AcWing 852 spafa 判断负环(模板题(2))
  • Floyd
    • 模板
    • AcWing 854 Floyd 求最短路(模板题)
  • 最小生成树
  • prim算法求最小生成树
    • 模板
    • AcWing 858. Prim算法求最小生成树(模板题)
  • kruskal
    • 思想
    • 模板
    • AcWing 859 Kruskal算法求最小生成树(模板题)
  • 染色法判定二分图
    • 思想
    • 模板
    • 染色法判定二分图(模板题)
  • 匈牙利算法(月佬算法)
    • 思想
    • 模板
    • AcWing 861 二分图的最大匹配(模板题)

数与图的存储方式

树与图的存储
树是一种特殊的图,与图的存储方式相同。
对于无向图中的边ab,存储两条有向边a->b, b->a。
因此我们可以只考虑有向图的存储。

(1) 邻接矩阵:g[a][b] 存储边a->b

(2) 邻接表:
对于每个点k,开一个单链表,存储k所有可以走到的点。h[k]存储这个单链表的头结点

int h[N], e[N], ne[N], idx;

添加一条边a->b

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

初始化

idx = 0;
memset(h, -1, sizeof h); //数组h元素全部初始化为-1

DFS

AcWing 842 排列问题

#include
using namespace std;
#define N 10

int a[N];
int st[N]; 
int n;


void dfs(int u)
{
	int i;
	if(u == n)
	{
		for(i = 0; i < n; i++)
		printf("%d ", a[i]);
		printf("\n");
		return;
	}
	for(i = 1; i <= n; i++)
	{
		if(!st[i])
		{
			a[u] = i;
			st[i] = true;
			dfs(u + 1);
			st[i] = false;  
		}
	}
}

int main()
{
	cin >> n;
	dfs(0);
	
	return 0;
 } 

AcWing 843 n-皇后问题

对于 dg[u + i] 和 udg[n - u + i]),表示正对角线与反对角线

u表示行,i表示列(u,i)
dg[u + i]

行/列 0 1 2 3
0 0 1 2 3
1 1 2 3 4
2 2 3 4 5
3 3 4 5 6

u表示行,i表示列(u,i)
udg[n-u + i]

行/列 0 1 2 3
0 4 5 6 7
1 3 4 5 6
2 2 3 4 5
3 1 2 3 4
#include 
using namespace std;
#define N 20 //对角线长 2 * n - 1,题目中 n < 10

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

void dfs(int u)
{
	int i;
	if(u == n)
	{
		for(i = 0; i < n; i++) puts(g[i]);
		puts("");
		return;
	}
	for(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;
	int i, j;
	for(i = 0; i < n; i++)
	 for(j = 0; j < n; j++)
	 g[i][j] = '.';
	 
	dfs(0);	
	return 0;
}

BFS

AcWing 844 走迷宫

#include
#include
#include 
using namespace std;
typedef  pair<int, int> PII;
#define N 110

int g[N][N];
int d[N][N];
PII q[N * N];

int n, m, i, j;

int bfs()
{
	int hh = 0, tt = 0;
	q[0] = {0, 0};
	
	int dx[4] = {-1, 0, 1, 0};
	int dy[4] = {0, 1, 0, -1};
	memset(d, -1, sizeof d);
	d[0][0] = 0;
	while(hh <= tt)
	{
		PII t = q[hh++];
		for(i = 0; i < 4; i++)
		{
			int x = t.first + dx[i];
			int y = t.second + dy[i];
			if(x >= 0 && x < n && y >= 0 && y < m && d[x][y] == -1 && g[x][y] == 0)
			{
				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(i = 0; i < n; i++)
	{
		for(j = 0; j < m; j++)
		scanf("%d",&g[i][j]);
	}
	
	cout << bfs() << endl;
	return 0;
}

AcWing 845 八数码

#include
#include
#include
#include
using namespace std;

int bfs(string start)
{
	queue<string> q;
	unordered_map<string, int> d;
	string end = "12345678x";
	int dx[4] = {-1, 0, 1, 0};
	int dy[4] = {0, 1, 0, -1};
	d[start] = 0;
	q.push(start);
	while(q.size())
	{
		auto t = q.front();
		q.pop();
		int distance = d[t];
		if(t == end) return distance;
		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[k], t[3 * a + b]);
				if(!d.count(t))
				{
					d[t] = distance + 1;
					q.push(t);
				}
				swap(t[k], t[3 * a + b]);
			}
		}
	}
	return -1;
}

int main()
{
	int i;
	string s;
	for(i = 0; i < 9; i++)
	{
		char c;
		cin >> c; 
		s += c;
	}
	cout << bfs(s) << endl;
	return 0;
}

树与图的深度优先遍历

模板

(1) 深度优先遍历

void dfs(int u)
{
    st[u] = true; // st[u] 表示点u已经被遍历过

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

AcWing 846 树的重心(模板题)

#include
#include
#include
using namespace std;
#define N 100010
int h[N], e[N], ne[N], idx;
int n;
bool st[N];
int ans = N;
void add(int a, int b)
{
	e[idx] = a; ne[idx] = h[b]; h[b] = idx++;
}

int dfs(int u)
{
	st[u] = true;
	int res, sum = 1;	
	for(int i = h[u]; i != -1; i = ne[i])
	{
		int j = e[i];
		if(!st[j])
		{
			int s = dfs(j);
			sum += s;
			res = max(res, s);
		}
	}
	res = max(res, n - sum);
	ans = min(ans, res);
	return sum;
}

int main()
{
	int i;
	cin >> n;
	memset(h, -1, sizeof h);
	for(i = 1; i < n; i++)
	{
		int a, b;
		scanf("%d%d",&a, &b);
		add(a, b);
		add(b, a);
	}
	dfs(1);
	cout << ans <<endl;
	
	return 0;
 } 

树与图的广度优先遍历

模板

(2) 宽度优先遍历

queue<int> q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);

while (q.size())
{
    int t = q.front();
    q.pop();

    for (int i = h[t]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!s[j])
        {
            st[j] = true; // 表示点j已经被遍历过
            q.push(j);
        }
    }
}

AcWing 847 图中点的层次(模板题)

#include
#include
using namespace std;
#define N 100010
int n, m, i;
int e[N], h[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++;
}


int bfs()
{
	memset(d, -1, sizeof d);
	int hh = 0, tt = 0;
	q[0] = 1;
	d[1] = 0;
	while(hh <= tt)
	{
		int t = q[hh++];
		for(i = h[t]; i != -1; i = ne[i])
		{
			int j = e[i];
			if(d[j] == -1)
			{
				q[++tt] = j;
				d[j] = d[t] + 1;
			}
		}
	}
	return d[n];
}

int main()
{
	cin >> n >> m;
	memset(h, -1, sizeof h);	
	for(i = 0; i < m; i++)
	{
		int a, b;
		cin >> a >> b;
		add(a, b);	
	}		
	cout << bfs() << endl;
	return 0;
 } 

拓扑排序

模板

bool topsort()
{
    int hh = 0, tt = -1;

    // d[i] 存储点i的入度
    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];
            if (-- d[j] == 0)
                q[ ++ tt] = j;
        }
    }

    // 如果所有点都入队了,说明存在拓扑序列;否则不存在拓扑序列。
    return tt == n - 1;
}

AcWing 848 有向图的拓扑序列(模板题)

#include
#include
using namespace std;
#define N 100010
int n, m, i;
int e[N], h[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++;
}
bool topsort()
{
	int hh = 0, tt = -1;
	for(i = 1; i <= n; i++)
	{
		if(!d[i])
		q[++tt] = i;
	}
	
	while(hh <= tt)
	{
		int t = q[hh++];
		for(i = h[t]; i != -1; i = ne[i])
		{
			int j = e[i];
			if(--d[j] == 0)
			q[++tt] = j;
		}
	}
	return tt == n - 1;
}

int main()
{
	cin >> n >> m;
	memset(h, -1, sizeof h);
	for(i = 0; i < m; i++)
	{
		int x, y;
		scanf("%d%d",&x, &y);
		add(x, y);
		d[y]++;
	}
	if(topsort())
	{
		for(i = 0 ; i < n ; i++)
		printf("%d ",q[i]);
	}
	else
		printf("-1");
	puts("");
	return 0;
 } 

最短路径

acwing算法基础课-第三章 搜索与图论_第1张图片
原图来自acwing算法基础课

Dijkstra

采用贪心算法的策略

模板

(1). 朴素dijkstra算法-模板题 AcWing 849. Dijkstra求最短路 I
O(n^2),n为点

int g[N][N];  // 存储每条边
int dist[N];  // 存储1号点到每个点的最短距离
bool st[N];   // 存储每个点的最短路是否已经确定

// 求1号点到n号点的最短路,如果不存在则返回-1
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[t] > dist[j]))
                t = j;

        // 用t更新其他点的距离
        for (int j = 1; j <= n; j ++ )
            dist[j] = min(dist[j], dist[t] + g[t][j]);

        st[t] = true;
    }

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

(2). 堆优化版dijkstra -模板题 AcWing 850. Dijkstra求最短路 II
O(mlogn),m为边,n为点

typedef pair<int, int> PII;

int n;      // 点的数量
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N];        // 存储所有点到1号点的距离
bool st[N];     // 存储每个点的最短距离是否已确定

// 求1号点到n号点的最短距离,如果不存在,则返回-1
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, 1});      // first存储距离,second存储节点编号

    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;
    return dist[n];
}

AcWing 849 Dijkstra 求最短路径 I (模板题(1))

#include
#include
using namespace std;

#define N 510
int n, m, i, j;
int g[N][N];
bool st[N];
int disk[N];


int Dijkstra()
{
	memset(disk, 0x3f, sizeof disk);
	disk[1] = 0;
	
	for(i = 0; i < n - 1; i++)
	{
		int t = -1;
		for(j = 1; j <= n; j++)
		if(!st[j] && (t == -1 || disk[t] > disk[j]))
		t = j;
		
		for(j = 1; j <= n; j++)
		disk[j] = min(disk[j], disk[t] + g[t][j]);
		
		st[t] = true;
	}
	if(disk[n] == 0x3f3f3f3f) return -1;
	return disk[n];
	
}

int main()
{
	cin >> n >> m;
	memset(g, 0x3f, sizeof g);
	while(m--)
	{
		int x, y, w;
		scanf("%d%d%d", &x, &y, &w);
		g[x][y] = min(g[x][y], w);
	}
	
	cout << Dijkstra() <<endl;
	return 0;
}

AcWing 850 Dijkstra 求最短路 II (模板题(2))

#include
#include
#include
using namespace std;
#define N 100010
typedef pair<int, int> PII;
int e[N], h[N], ne[N], idx, w[N];
bool st[N];
int disk[N];
int i, j, n, m;

void add(int x, int y, int z)
{
	e[idx] = y; ne[idx] = h[x]; w[idx] = z; h[x] = idx++;
}

int Dijkstra()
{
	memset(disk, 0x3f, sizeof disk);
	disk[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(i = h[ver]; i != -1; i = ne[i])
		{
			 j = e[i];
			if(disk[j] > distance + w[i])
			{
				disk[j] = distance + w[i];
				heap.push({disk[j], j});
			}
		}
	}
	if(disk[n] == 0x3f3f3f3f) return -1;
	return disk[n]; 
}

int main()
{
	cin >> n >> m;
	memset(h, -1, sizeof h);
	while(m--)
	{
		int  x, y, z;
		cin >> x >> y >> z;
		add(x, y, z);
	}
	
	cout << Dijkstra() << endl;
	return 0;
 } 

bellman_ford

模板

ellman-Ford算法 —— 模板题 AcWing 853. 有边数限制的最短路
时间复杂度 O(nm), n表示点数, m表示边数
注意在模板题中需要对下面的模板稍作修改,加上备份数组,详情见模板题。

int n, m;       // n表示点数,m表示边数
int dist[N];        // dist[x]存储1到x的最短路距离

struct Edge     // 边,a表示出点,b表示入点,w表示边的权重
{
    int a, b, w;
}edges[M];

// 求1到n的最短路距离,如果无法从1走到n,则返回-1。
int bellman_ford()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    // 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。
    for (int i = 0; i < n; i ++ )
    {
        for (int j = 0; j < m; j ++ )
        {
            int a = edges[j].a, b = edges[j].b, w = edges[j].w;
            if (dist[b] > dist[a] + w)
                dist[b] = dist[a] + w;
        }
    }

    if (dist[n] > 0x3f3f3f3f / 2) return -1;
    return dist[n];
}

AcWnig 853 有边路限制的最短路(模板题)

题目要求从 1号点到 n号点的最多经过 k 条边的最短距离。
加上备份数组,以免发生“串联更新”,即 每次我只能更新距1号点长度为 i 的边(i = 1, 2,3…,k),而不能更新其后面的点

#include
#include
using namespace std;
#define N 510
#define M 10010
struct Edge{
	int a;
	int b;
	int w;
}edges[M]; 

int dist[N];
int backup[N];
int n, m, k;
int i, j;

int bellman_ford()
{
	memset(dist, 0x3f, sizeof dist);
	dist[1] = 0;
	for(i = 0; i < k; i++)
	{
		memcpy(backup, dist, sizeof dist);
		for(j = 0; j < m; j++)
		{
			int a = edges[j].a, b= edges[j].b, c= edges[j].w;
			if(dist[b] > backup[a] + c)
				dist[b] = backup[a] + c;
		 } 
	}
	if(dist[n] > 0x3f3f3f3f / 2) return 0x3f3f3f3f;
	return dist[n];
}
int main()
{
	cin >> n >> m >> k;	
	for(i = 0; i < m; i++)
	{
		int x, y, z;
		scanf("%d%d%d", &x, &y, &z);
		edges[i] = {x, y, z};
	}
	int t = bellman_ford();
	
	if( t == 0x3f3f3f3f) printf("impossible\n");
	else printf("%d\n", t);
	
	return 0;
}

spfa

模板

(1). spfa 算法(队列优化的Bellman-Ford算法) —— 模板题 AcWing 851. spfa求最短路
该模板(1),数据必须保证不存在负权回路
时间复杂度 平均情况下 O(m)O(m),最坏情况下 O(nm), n 表示点数,m 表示边数

int n;      // 总点数
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N];        // 存储每个点到1号点的最短距离
bool st[N];     // 存储每个点是否在队列中

// 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1
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])     // 如果队列中已存在j,则不需要将j重复插入
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

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

(2). spfa判断图中是否存在负环 —— 模板题 AcWing 852. spfa判断负环
常用该模板(2)判断图中是否存在负环

int n;      // 总点数
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N], cnt[N];        // dist[x]存储1号点到x的最短距离,cnt[x]存储1到x的最短路中经过的点数
bool st[N];     // 存储每个点是否在队列中

// 如果存在负环,则返回true,否则返回false。
bool spfa()
{
    // 不需要初始化dist数组
    // 原理:如果某条最短路径上有n个点(除了自己),那么加上自己之后一共有n+1个点,由抽屉原理一定有两个点相同,所以存在环。

    queue<int> q;
    for (int i = 1; i <= n; i ++ )
    {
        q.push(i);
        st[i] = 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];
                cnt[j] = cnt[t] + 1;
                if (cnt[j] >= n) return true;       // 如果从1号点到x的最短路中包含至少n个点(不包括自己),则说明存在环
                if (!st[j])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    return false;
}

AcWing 851 spfa求最短路 (模板题(1))

#include
#include
#include
using namespace std;

#define N 100010
int e[N], w[N], ne[N], idx, h[N];
int n, m;
int i, j;
int dist[N];
bool st[N];


void add(int x, int y, int z)
{
	e[idx] = y; w[idx] = z; ne[idx] = h[x]; h[x] = 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(i = h[t]; i != -1; i = ne[i])
		{
			j = e[i];
			if(dist[j] > dist[t] + w[i])
			{
				dist[j] = dist[t] + w[i];
				if(!st[j])
				{
					q.push(j);
					st[j] = true;
				}
			}
		}
	}
	if(dist[n] == 0x3f3f3f3f) return 0x3f3f3f3f;
	return dist[n];
	
}

int main()
{
	cin >> n >> m;
	memset(h, -1, sizeof h);
	while(m--)
	{
		int x, y, z;
		scanf("%d%d%d", &x, &y, &z);
		add(x, y, z);
	}
	int t = spfa();
	if(t == 0x3f3f3f3f) printf("impossible\n");
	else printf("%d\n", t);
	
	return 0;
 } 

AcWing 852 spafa 判断负环(模板题(2))

#include
#include
#include
using namespace std;
#define N 2010
#define M 10010
int e[M], h[N], w[M], ne[M], idx;
int cnt[N];
int dist[N];
bool st[N];
int n, m, i, j; 

void add(int x, int y, int z)
{
	e[idx] = y; ne[idx] = h[x], w[idx] = z; h[x] =idx++;
}

bool spfa()
{
	queue<int> q;
	for(i = 1; i <= n; i++)
	{
		q.push(i);
		st[i] = true;
	}
	
	while(q.size())
	{
		auto t = q.front();
		q.pop();
		st[t] = false;
		for(i = h[t]; i != -1; i = ne[i])
		{
			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];	
				}	
			}
		}
	}
	return false;
}

int main()
{
	cin >> n >> m;
	memset(h, -1, sizeof h);
	while(m--)
	{
		int x, y, z;
		scanf("%d%d%d", &x, &y, &z);
		add(x, y, z);
	}
	if(spfa()) printf("Yes\n");
	else printf("No\n");
	return 0;
 } 

Floyd

利用动态规划的思想

模板

floyd算法 —— 模板题 AcWing 854. Floyd求最短路
时间复杂度是 O(n^3), n 表示点数

初始化:
    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;

// 算法结束后,d[a][b]表示a到b的最短距离
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]);
}

AcWing 854 Floyd 求最短路(模板题)

#include 
#include
#include
using namespace std;
#define N 210
int g[N][N];
int n, m ,k, i ,j, p;

void Floyd()
{
	for(p = 1; p <= n; p++)
	for(i = 1; i <= n; i++)
	for(j = 1; j <= n; j++)
	g[i][j] = min(g[i][j], g[i][p] + g[p][j]);
}

int main()
{
	cin >> n >> m >> k;
	memset(g, 0x3f, sizeof g);
	for(i = 1; i <= n; i++) g[i][i] = 0;
	while(m--)
	{
		int x, y, z;
		scanf("%d%d%d", &x, &y, &z);
		g[x][y] = min(g[x][y], z);
	}
	Floyd();
	while(k--)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		if(g[a][b] > 0x3f3f3f3f / 2) printf("impossible\n");
		else
		printf("%d\n", g[a][b]);
	}
	
	return 0;
}

最小生成树

acwing算法基础课-第三章 搜索与图论_第2张图片
来自acwing课

prim算法求最小生成树

prim算法与dijkstra算法非常相似,dijkstra算法是计算到顶点的距离,而Prim算法是计算到集合的距离

模板

朴素版prim算法 —— 模板题 AcWing 858. Prim算法求最小生成树
时间复杂度是 O(n^2) n 表示点数

int n;      // n表示点数
int g[N][N];        // 邻接矩阵,存储所有边
int dist[N];        // 存储其他点到当前最小生成树的距离
bool st[N];     // 存储每个点是否已经在生成树中


// 如果图不连通,则返回INF(值是0x3f3f3f3f), 否则返回最小生成树的树边权重之和
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[t] > dist[j]))
                t = j;

        if (i && dist[t] == INF) return INF;

        if (i) res += dist[t];
        st[t] = true;

        for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);
    }

    return res;
}

AcWing 858. Prim算法求最小生成树(模板题)

#include
#include
#include
using namespace std;
#define N 510

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

int prim()
{
	memset(dist, 0x3f3f3f3f, sizeof dist);
	int res = 0;
	for(i = 0; i < n; i++)
	{
		int t = -1;
		for(j = 1; j <= n; j++) //这里需迭代 n 次,因为第一个点未确定,而dijkstra算法只需 n-1 ,因为第一个点未确定 
		if(!st[j] && (t == -1 || dist[t] > dist[j]))
		t = j;
		
		if(i && dist[t] == 0x3f3f3f3f) return 0x3f3f3f3f;
		if(i) res += dist[t];
		st[t] = true;
		//更新为到集合的最短距离 
		for(j = 1; j <= n; j++) dist[j] = min(dist[j], g[t][j]);
	}
	return res;
}

int main()
{
	cin >> n >> m;
	memset(g, 0x3f, sizeof g);
	while(m--)
	{
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		g[u][v] = g[v][u] = min(g[u][v], w);
	}
	
	int t = prim();
	if(t == 0x3f3f3f3f) 
		printf("impossible\n");
	else printf("%d", t);
	return 0;
 } 

kruskal

思想

思路

1.给每条边从小到大排序
2.从小到大遍历每条边,如果不在a,b不在同一集合里,将a, b变成同一集合(并查集)

模板

Kruskal算法 —— 模板题 AcWing 859. Kruskal算法求最小生成树
时间复杂度是 O(mlogm), m 表示边数

int n, m;       // n是点数,m是边数
int p[N];       // 并查集的父节点数组

struct Edge     // 存储边
{
    int a, b, w;

    bool operator< (const Edge& W)const
    {
        return w < W.w;
    }
}edges[M];

int find(int x)     // 并查集核心操作
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int kruskal()
{
    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) return INF;
    return res;
}

AcWing 859 Kruskal算法求最小生成树(模板题)

#include
#include
#include
#define N 100010
#define M 200010
using namespace std;

int n, m, i, j;
int p[N];

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

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

int kruskal()
{
	sort(edges, edges + m);
	int res = 0, cnt = 0;
	for(i = 1; i<= n; i++) p[i] = i;
	
	for(i = 0; i < m; i++)
	{
		int a = edges[i].a, b= edges[i].b, c = edges[i].c;
		a = find(a), b = find(b);
		if(a != b)
		{
			p[a] = b;
			res += c;
			cnt++; //最小生成树边数
		 } 
	} 
//最小生成树边数应为 n - 1 ,
	if(cnt < n - 1) return 0x3f3f3f3f;
	return res;
}

int main()
{
	cin >> n >> m;
	for(i = 0; i < m; i++)
	{
		int x, y, w;
		scanf("%d%d%d", &x, &y, &w);
		edges[i] = {x, y, w};	
	}
	int t = kruskal();
	if(t == 0x3f3f3f3f) printf("impossible\n");
	else printf("%d\n", t);
	return 0;
}

染色法判定二分图

思想

定理:二分图当且仅当图不含奇数环

充分性证明:
假设一个图不含奇数环,我们需要证明这个图是一个二分图。
我们可以通过染色法来证明。具体来说,我们随便选取一个顶点,将它染成红色。然后,将与它相邻的所有顶点都染成蓝色。接下来,将与这些蓝色顶点相邻的所有顶点都染成红色。依次往复,直到所有的顶点都被染色。如果在这个过程中出现了相邻的两个顶点颜色相同的情况,那么这个图一定含有奇数环,与假设矛盾。因此,染色后得到的图是一个二分图。
必要性证明:
若G=(X∪Y)二分图,假定p:v0v1v2…vkv0是一条回路,并令v0∈X。则对所有的i≥0,有v2i∈X,v2i+1∈Y。因为vk∈Y,则存在某个i使得k=2i+1,则p的长度为2i+2.所以G中没有奇数长度的回路。

模板

染色法判别二分图 —— 模板题 AcWing 860. 染色法判定二分图
时间复杂度是 O(n+m), n 表示点数,m 表示边数

int n;      // n表示点数
int h[N], e[M], ne[M], idx;     // 邻接表存储图
int color[N];       // 表示每个点的颜色,-1表示未染色,0表示白色,1表示黑色

// 参数:u表示当前节点,c表示当前点的颜色
bool dfs(int u, int c)
{
    color[u] = c;
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (color[j] == -1)
        {
            if (!dfs(j, !c)) return false;
        }
        else if (color[j] == c) return false;
    }

    return true;
}

bool check()
{
    memset(color, -1, sizeof color);
    bool flag = true;
    for (int i = 1; i <= n; i ++ )
        if (color[i] == -1)
            if (!dfs(i, 0))
            {
                flag = false;
                break;
            }
    return flag;
}

染色法判定二分图(模板题)

#include
#include
#include
#define N 100010
#define M 200020
using namespace std;

bool flag = true;	
int n, m;
int e[M], ne[M], h[N], idx;
int color[N];

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

bool dfs(int u, int c)
{
	color[u] = c;
	for(int i = h[u]; i != -1; i = ne[i])
	{
		int j = e[i];
		if(color[j] == -1)
		{
			if(!dfs(j, !c)) return false;
		}
		else if(color[j] == c) return false;
	}
	return true;
}

int main()
{
	cin >> n >> m;
	memset(h, -1, sizeof h);
	while(m--)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		add(u, v), add(v, u);
	}
	memset(color, -1, sizeof color);
	for(int i = 1; i <= n; i++)
	{
		if(color[i] == -1)
		{
			if(!dfs(i, 0))
			{
				flag = false;
				break;
			}
		}
	}
	if(flag) puts("Yes");
	else puts("No");
	return 0;
}

匈牙利算法(月佬算法)

思想

二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的集 E 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。
二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。
建立有向图G
思路:分为二分图的左侧和右侧。 优先选择左侧序号更小的连接可能的边。 对于两个点的目标点“冲突”的时候,采取“协商”的办法。 即序号小的连接可能连接的另一条边。 若“协商”失败,则放弃序号较大的点的边。
重点:st[]的作用:
首先我们find(x) 遍历属于h[x]的单链表相当于遍历他所有喜欢的女生
如果某个女生j没有被他预定过的话,就标记这个女生j被他预定,st[j]=true。
这时如果女j还没有匹配过,即match[j]==0的时候,那这个预定就成真了,得到match[j]=x,
而如果女j之前就被男k匹配过了,那我们就find(k),也就是find(match[j])(因为原本match[j]==k)
然后在find(k)的过程中,因为st[j]=true,这时候男k就不能再选则女j了,因为女j已经被预定了,所以男k就只能在他喜欢的女生里面选择其他人来匹配。
当然,如果find(k)返回false的话,那就 if(match[j]==0||find(match[j]))都不成立,那男j就一边玩去吧~

转载自 蒟蒻豆进阶之路

模板

匈牙利算法 —— 模板题 AcWing 861. 二分图的最大匹配
时间复杂度是 O(nm), n 表示点数,m 表示边数,实际远小于O(nm)

int n1, n2;     // n1表示第一个集合中的点数,n2表示第二个集合中的点数
int h[N], e[M], ne[M], idx;     // 邻接表存储所有边,匈牙利算法中只会用到从第二个集合指向第一个集合的边,所以这里只用存一个方向的边
int match[N];       // 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个
bool st[N];     // 表示第二个集合中的每个点是否已经被遍历过

bool find(int x)
{
    for (int i = h[x]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j])
        {
            st[j] = true;
            if (match[j] == 0 || find(match[j]))
            {
                match[j] = x;
                return true;
            }
        }
    }

    return false;
}

// 求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点
int res = 0;
for (int i = 1; i <= n1; i ++ )
{
    memset(st, false, sizeof st);
    if (find(i)) res ++ ;
}

AcWing 861 二分图的最大匹配(模板题)

#include
#include
#include
#define N 510
#define M 100010
using namespace std;

int e[M], ne[M], h[N], idx;
int n1, n2, m;
bool st[N];
int match[N];
int res;
void add(int a, int b)
{
	e[idx] = b; ne[idx] = h[a]; h[a] = idx++;
}
bool find(int x)
{
	for(int i = h[x]; i != -1; i = ne[i])
	{
		int j = e[i];
		if(!st[j])
		{
			st[j] = true;
			if(match[j] == 0 || find(match[j]))
			{
				match[j] = x;
				return true;
			}
		}
	}
	return false;
}

int main()
{
	cin >> n1 >> n2 >> m;
	memset(h, -1, sizeof h);
	while(m--)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		add(a, b);
	}
	for(int i = 1; i <= n1; i++)
	{
		memset(st, false, sizeof st);
		if(find(i)) res++;
	}
	printf("%d", res);
	return 0;
}

你可能感兴趣的:(#,算法,-,acwing算法基础课,图论,算法)