9.21 图论测试 题解
9.21 图论测试 题解
A. 通讯(Kruskal 最小生成森林)
题意
给定平面上的 \(P\) 个哨所的坐标。哨所之间可以用无线电收发器或者卫星电话通话。每个哨所都有无线电收发器,但是配备卫星电话的哨所最多只能有 \(S\) 个。
求出最小的 用无线电收发器通话的最大距离 \(D\),使得每一对哨所之间至少有一条通话路径(直接的或者间接的)。
思路
Kruskal 贪心连接距离最近的 \(P-S\) 个哨所,剩下 \(S\) 个哨所用卫星电话连接。
Kruskal 算法最后一个连接的边即为最小的最大距离。
代码
#include <iostream>
#include <algorithm>
#include <cmath>
#include <iomanip>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define il inline
using namespace std;
typedef long long ll;
typedef double db;
int const N = 550;
int S, P;
int x[N], y[N];
il db cal(int a, int b, int c, int d) {
return sqrt((a - c) * (a - c) + (b - d) * (b - d));
}
struct Edge{
int u, v;
db w;
} e[N * N];
int fa[N], cnt;
int getfa(int x) { return x == fa[x] ? x : fa[x] = getfa(fa[x]); }
db Kruskal() {
int tot = 0;
f(i, 1, P) fa[i] = i;
sort(e + 1, e + cnt + 1, [](Edge p, Edge q) { return p.w < q.w; });
f(i, 1, cnt) {
int fx = getfa(e[i].u), fy = getfa(e[i].v);
if (fx != fy) {
fa[fx] = fy;
++tot;
}
if (tot == P - S) {
return e[i].w;
}
}
return -1;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> S >> P;
f(i, 1, P) {
cin >> x[i] >> y[i];
f(j, 1, i - 1) {
e[++cnt] = {i, j, cal(x[i], y[i], x[j], y[j])};
}
}
cout << fixed << setprecision(2) << Kruskal() << '\n';
return 0;
}
B. 序列(拓扑排序)
题意
给定一个有向无环图,如果图的拓扑序唯一,输出一行一个字符串 YES,否则输出 NO。
思路
在拓扑排序过程中,如果队列中同时存在多于一个点,即同时有多于一个点满足入度为 \(0\),那么说明拓扑序不唯一。
代码
#include <cstdio>
#include <queue>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define il inline
using namespace std;
int const N = 1e5 + 10;
int n, m, deg[N];
queue<int> q;
struct Edge{
int to, nxt;
} e[N];
int head[N], cnt;
il void add(int from, int to) {
e[++cnt].to = to, e[cnt].nxt = head[from], head[from] = cnt;
return;
}
signed main() {
scanf("%d%d", &n, &m);
f(i, 1, m) {
int x, y;
scanf("%d%d", &x, &y);
add(x, y);
++deg[y];
}
f(i, 1, n) if (!deg[i]) q.push(i);
while (!q.empty()) {
int t = q.front();
q.pop();
if (!q.empty()) {
puts("NO");
return 0;
}
for (int i = head[t]; i; i = e[i].nxt) {
int to = e[i].to;
--deg[to];
if (!deg[to]) q.push(to);
}
}
puts("YES");
return 0;
}
C. 移动(缩点 + 拓扑图上 DP)
题意
给定一个有向图,从图的 \(1\) 号节点出发,途中可以反向经过一条边,最后回到 \(1\) 号点。点和边可以重复经过。
求最多能经过的点的数量(同一个点被经过多次也只算一个点) 。
思路
由于点和边可以重复经过,所以图中的同一个强连通分量中的点可以看作同一点。
因此可以把有向图缩点变成一个 DAG(有向无环图),DAG 中点的权值即为 SCC(强连通分量)的大小。
以下讨论的均为在缩点后的 DAG 中。
设从 \(i\) 到 \(j\) 最多能经过的点的数量为 \(f(i,j)\)。那么答案一定为 \(f(1,u)+f(v,1)\),其中 \(u,v\) 分别是图中某一条边的起点和终点。
我们可以在拓扑排序的过程中 dp 处理出所有 \(f(1,i)\);然后建反图,同样地处理出 \(g(1,j)\),那么 \(g(1,j)=f(j,1)\)。
之后枚举正向图中的所有边 \(u\rightarrow v\),用 \(f(1,u)+g(1,v)-size(1)\) 更新答案,其中 \(size(1)\) 即为点 \(1\) 所在的 SCC 的大小。
代码
#include <iostream>
#include <stack>
#include <queue>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define il inline
using namespace std;
int const N = 1e5 + 10;
int n, m;
int head[N], cnt;
int head1[N], cnt1, fr[N];
int head2[N], cnt2;
struct Edge{
int to, nxt;
} e[N << 1], e1[N << 1], e2[N << 1];
il void add(int from, int to) {
e[++cnt].to = to, e[cnt].nxt = head[from], head[from] = cnt;
return;
}
il void add1(int from, int to) {
e1[++cnt1].to = to, e1[cnt1].nxt = head1[from], head1[from] = cnt1;
fr[cnt1] = from;
return;
}
il void add2(int from, int to) {
e2[++cnt2].to = to, e2[cnt2].nxt = head2[from], head2[from] = cnt2;
return;
}
int dfn[N], low[N], idx;
int scc[N], tot, v[N]; //v:SCC中的点的个数
stack<int> st;
bool inStack[N];
void Tarjan(int x) {
dfn[x] = low[x] = ++idx;
st.push(x);
inStack[x] = true;
for (int i = head[x]; i; i = e[i].nxt) {
const int &to = e[i].to;
if (!dfn[to]) {
Tarjan(to);
low[x] = min(low[x], low[to]);
} else if (inStack[to]) {
low[x] = min(low[x], dfn[to]);
}
}
if (dfn[x] == low[x]) {
int t;
++tot;
while (!st.empty()) {
t = st.top();
st.pop();
inStack[t] = false;
scc[t] = tot;
++v[tot];
if (t == x) break;
}
}
return;
}
int dp1[N], dp2[N];
queue<int> q1, q2;
bool inQueue[N];
void Toposort1() {
q1.push(scc[1]);
dp1[scc[1]] = v[scc[1]];
inQueue[scc[1]] = true;
while (!q1.empty()) {
int t = q1.front();
q1.pop();
inQueue[t] = false;
for (int i = head1[t]; i; i = e1[i].nxt) {
int to = e1[i].to;
if (dp1[to] < dp1[t] + v[to]) {
dp1[to] = dp1[t] + v[to];
if (!inQueue[to]) {
inQueue[to] = true;
q1.push(to);
}
}
}
}
return;
}
void Toposort2() {
memset(inQueue, 0, sizeof inQueue);
q2.push(scc[1]);
dp2[scc[1]] = v[scc[1]];
inQueue[scc[1]] = true;
while (!q2.empty()) {
int t = q2.front();
q2.pop();
inQueue[t] = false;
for (int i = head2[t]; i; i = e2[i].nxt) {
int to = e2[i].to;
if (dp2[to] < dp2[t] + v[to]) {
dp2[to] = dp2[t] + v[to];
if (!inQueue[to]) {
inQueue[to] = true;
q2.push(to);
}
}
}
}
return;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m;
int x, y;
f(i, 1, m) {
cin >> x >> y;
add(x, y);
}
f(i, 1, n) if (!dfn[i]) idx = 0, Tarjan(i);
f(i, 1, n) {
for (int k = head[i]; k; k = e[k].nxt) {
int j = e[k].to;
if (scc[i] == scc[j]) continue;
add1(scc[i], scc[j]);
add2(scc[j], scc[i]);
}
}
Toposort1();
Toposort2();
int ans = v[scc[1]];
f(i, 1, cnt1)
if (dp1[e1[i].to] && dp2[fr[i]])
ans = max(ans, dp1[e1[i].to] + dp2[fr[i]] - v[scc[1]]);
cout << ans << '\n';
return 0;
}
D. 删除(Floyd + 逆向思维)
题意
一个完全有向图,\(n\) 个节点,每条边上有一个权值,\(x\) 到 \(y\) 的边的权值为 \(a[x,y]\)。给定 \(n\) 次删除操作,每次删去一个点,即删掉连入这个点和这个点连出的所有边。
求每次删点后图中现存的点任意两点间的最短路之和。即,设剩下的点集为 \(S\),求
其中 \(\mathrm{dis}(x,y)\) 表示 \(x\) 到 \(y\) 的最短路。
对于 \(100\%\) 的数据:\(1 \le n \le 500,1 \le a[i,j] \le 10^5\ (i\ne j) ,a[i,i] = 0\)。
思路
每次删点的过程,反过来即为不断加点的过程。
我们可以将删点操作离线,在反过来不断加点的过程中,用这次加的点去松弛所有边。
所以可以在 Floyd 枚举中间点的过程看成加点的过程,枚举要松弛的点的同时统计答案。
代码
#include <cstdio>
#include <cstring>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define g(x, y, z) for (int x = (y); (x) >= (z); --(x))
#define il inline
#define int ll
using namespace std;
typedef long long ll;
int const N = 550;
int n, a[N][N], d[N][N];
int del[N];
int sum, ans[N];
signed main() {
// freopen("erase7.in", "r", stdin);
// freopen("erase7.out", "w", stdout);
scanf("%d", &n);
if (n == 1) {
putchar('0');
return 0;
}
f(i, 1, n) f(j, 1, n) scanf("%d", &a[i][j]);
g(i, n, 1) scanf("%d", &del[i]);
f(ii, 1, n) {
int i = del[ii];
f(jj, 1, n) {
int j = del[jj];
d[ii][jj] = a[i][j];
}
}
f(k, 1, n) {
sum = 0;
f(i, 1, n) f(j, 1, n) {
if (d[i][j] > d[i][k] + d[k][j])
d[i][j] = d[i][k] + d[k][j];
if (i <= k && j <= k) sum += d[i][j];
}
ans[n - k + 1] = sum;
}
f(i, 1, n) printf("%d ", ans[i]);
putchar('\n');
return 0;
}

浙公网安备 33010602011771号