树与图的存储
树是一种特殊的图,与图的存储方式相同。
对于无向图中的边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
#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;
}
对于 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;
}
#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;
}
#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);
}
}
#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);
}
}
}
#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;
}
#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;
}
采用贪心算法的策略
(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];
}
#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;
}
#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;
}
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];
}
题目要求从 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;
}
(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;
}
#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;
}
#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算法 —— 模板题 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]);
}
#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;
}
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;
}
#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;
}
思路
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;
}
#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 ++ ;
}
#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;
}