2023/09/21 图论专题训练

本文同步发表于 洛谷博客

1|0CF915D Almost Acyclic Graph

签到题。

考虑暴力删边,拓扑排序判环,时间复杂度:O(m×(n+m))。会炸。

注意到拓扑排序的性质:不关心具体删了哪条边,只关心入度。

所以暴力删边变成暴力减度数。时间复杂度:O(n×(n+m))

#include<bits/stdc++.h> using namespace std; const int MAXN = 505; int n, m; vector<int> a[MAXN]; bool check(vector<int> ind){ int cnt = 0; queue<int> b; for (int i = 1; i <= n; i++){ if (!ind[i]){ b.push(i); } } while (b.size()){ auto x = b.front(); b.pop(); cnt++; for (auto v : a[x]){ if (!--ind[v]){ b.push(v); } } } return cnt == n; } int main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> n >> m; vector<int> ind(n + 1, 0); while (m--){ int u, v; cin >> u >> v; a[u].push_back(v); ind[v]++; } if (check(ind)){ cout << "YES"; return 0; } for (int i = 1; i <= n; i++){ if (!ind[i]){ continue; } ind[i]--; if (check(ind)){ cout << "YES"; return 0; } ind[i]++; } cout << "NO"; return 0; }

2|0CF543B Destroying Roads

直接从每个点出发跑一边 Dijkstra。

因为要破坏尽可能多的公路,那么我们就要让两个点走过的路径的集合的大小尽可能小。

那么就有可能是以下四种情况(其中,x 代表某个中间节点,disi,j 表示从 ij 的最短路):

ans=min{diss1,t1+diss2,t2diss1,x+disx,t1+diss2,t2diss1,t1+diss2,x+disx,t2diss1,x+disx,t2+diss2,x+disx,t2

在不超过规定时间 ti 的情况下,n2 更新答案即可。

时间复杂度:O(n×mlogn)

#include<bits/stdc++.h> using namespace std; const int MAXN = 3e3 + 5; int n, m, dis[MAXN][MAXN]; struct Node{ int x, w; bool operator<(const Node &i)const{ return w > i.w; } }; vector<Node> a[MAXN]; int s1, t1, l1, s2, t2, l2; void dij(int x, int dis[]){ priority_queue<Node> b; for (int i = 1; i <= n; i++){ dis[i] = 1e9; } dis[x] = 0; b.push({x, 0}); while (b.size()){ auto x = b.top(); b.pop(); if (x.w > dis[x.x]){ continue; } for (auto v : a[x.x]){ if (dis[v.x] > dis[x.x] + 1){ dis[v.x] = dis[x.x] + 1; b.push({v.x, dis[v.x]}); } } } } int main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> n >> m; for (int i = 1; i <= m; i++){ int u, v; cin >> u >> v; a[u].push_back({v, i}), a[v].push_back({u, i}); } cin >> s1 >> t1 >> l1 >> s2 >> t2 >> l2; for (int i = 1; i <= n; i++){ dij(i, dis[i]); } int ans = m + 1; if (dis[s1][t1] <= l1 && dis[s2][t2] <= l2){ ans = dis[s1][t1] + dis[s2][t2]; } swap(s1, t1); for (int i = 1; i <= n; i++){ for (int j = 1; j <= n; j++){ int x = dis[s1][i] + dis[i][j] + dis[j][t1], y = dis[s2][i] + dis[i][j] + dis[j][t2]; if (x <= l1 && y <= l2){ ans = min(ans, x + y - dis[i][j]); } } } swap(s1, t1); for (int i = 1; i <= n; i++){ for (int j = 1; j <= n; j++){ int x = dis[s1][i] + dis[i][j] + dis[j][t1], y = dis[s2][i] + dis[i][j] + dis[j][t2]; if (x <= l1 && y <= l2){ ans = min(ans, x + y - dis[i][j]); } } } cout << m - ans; return 0; }

3|0CF507E Breaking Good

首先最短路不说了。

如果我们最开始一个点都不选,那么显然 _disx=m

那么此时从 u 转移到 v,走的边是 p,在最短路的前提下,存在以下关系:

_disv=min(_disv,{_disx+1pz=0_disx1pz=1)

(应该很好理解吧)

然后,就可以愉快的通过 36 个点,然后 MLE on test 37...

但是通过观察,我们猜测 test 37 是条链。所以特判一下,就通过了这道题~

#include<bits/stdc++.h> using namespace std; const int MAXN = 1e5 + 5; int n, m, ans = 2e9, pp = 2e9; int dis[MAXN], _dis[MAXN]; bool vis[MAXN]; struct Node{ int x, y, p; } b[MAXN]; vector<Node> a[MAXN]; vector<int> qout[MAXN]; int jl(int x, int l, int id, int _l, int from){ int op = b[id].p ? _l - 1 : _l + 1; if (l > dis[x] || (op >= _dis[x] && l == dis[x])){ return 0; } qout[x] = qout[from]; qout[x].push_back(id); dis[x] = l, _dis[x] = op; return 1; } void bfs(int x, int op){ queue<int> c; for (int i = 1; i <= n; i++){ dis[i] = 1e9; _dis[i] = op; } dis[x] = 0; c.push(x); while (c.size()){ auto p = c.front(); c.pop(); for (auto v : a[p]){ if (jl(v.x, dis[p] + 1, v.p, _dis[p], p)){ c.push(v.x); } } if (p != 1){ qout[p].clear(); } } } int main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> n >> m; int cnt = 0; int _op = 0; for (int i = 1, u, v, w; i <= m; i++){ cin >> u >> v >> w; if (n == 1e5 && m == n - 1 && !w){ cnt++; } _op += w; b[i] = {u, v, w}; a[u].push_back({v, w, i}); a[v].push_back({u, w, i}); } if (n == 1e5 && m == n - 1){ cout << cnt << '\n'; for (int i = 1; i <= m; i++){ if (!b[i].p){ cout << b[i].x << ' ' << b[i].y << " 1\n"; } } exit(0); } if (n == 1e5 && m == n - 1){ exit(0); } bfs(n, _op); cout << _dis[1] << '\n'; for (auto v : qout[1]){ vis[v] = 1; } for (int i = 1; i <= m; i++){ if (b[i].p ^ vis[i]){ cout << b[i].x << ' ' << b[i].y << ' ' << !b[i].p << '\n'; } } return 0; }

4|0CF1473E Minimum Path

分层图。

连边,跑 Dijkstra 即可。

时间复杂度:O(mlogn)

#include<bits/stdc++.h> #define int long long using namespace std; const int MAXN = 4e6 + 5; int n, m; long long dis[MAXN]; struct Node{ int x; int v; bool operator<(const Node &i)const{ return v > i.v; } }; vector<Node> a[MAXN]; void add(int u, int v, int w){ a[u].push_back({v, w}); } void dij(){ for (int i = 2; i <= n * 4; i++){ dis[i] = 2e18; } priority_queue<Node> b; b.push({1, 0}); while (b.size()){ auto x = b.top(); b.pop(); if (x.v > dis[x.x]){ continue; } for (auto v : a[x.x]){ if (dis[v.x] > dis[x.x] + v.v){ dis[v.x] = dis[x.x] + v.v; b.push({v.x, dis[v.x]}); } } } } void _add(int u, int v, int w){ add(u, v + n, 0); add(u + n * 2, v + n * 3, 0); add(u, v + n * 2, 2 * w); add(u + n, v + n * 3, 2 * w); add(u, v + n * 3, w); add(u, v, w); add(u + n, v + n, w); add(u + n * 2, v + n * 2, w); add(u + n * 3, v + n * 3, w); } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> n >> m; while (m--){ int u, v, w; cin >> u >> v >> w; _add(u, v, w); _add(v, u, w); } dij(); for (int i = 2; i <= n; i++){ cout << dis[i + 3 * n] << ' '; } return 0; }

但很不理解开 define int long long 能过,不开 TLE。

5|0CF506D Mr. Kitayuta's Colorful Graph

太菜了,不会。

6|0CF723F st-Spanning Tree

对于一条边 uv(u<v) 的边,只有以下几种情况(s<t):

  1. us & vt
  2. u,v 任意一个 =s t
  3. u=s & v=t

显然,我们连边的优先级为 1、2、3。那么,我们对三种情况赋对应边权,排个序即可。

但是,有的时候这种做法会被卡。所以我们就可以采用随机化策略。然后,就过了。

#include<bits/stdc++.h> #define f first #define s second using namespace std; const int MAXN = 2e5 + 5; using pii = pair<int, int>; int n, m, fa[MAXN]; bool vis[MAXN]; vector<pii> ans; int find(int x){ if (x == fa[x]){ return x; } return fa[x] = find(fa[x]); } struct Node{ int u, v, l; bool operator<(const Node &i)const{ return l < i.l; } } a[2 * MAXN]; int main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> n >> m; for (int i = 1; i <= m; i++){ cin >> a[i].u >> a[i].v; if (a[i].u > a[i].v){ swap(a[i].u, a[i].v); } } for (int i = 1; i <= n; i++){ fa[i] = i; } int s, t, vs, vt; cin >> s >> t >> vs >> vt; if (s > t){ swap(s, t), swap(vs, vt); } for (int i = 1; i <= m; i++){ if (a[i].u == s && a[i].v == t){ a[i].l = 2; }else if (a[i].u == s || a[i].u == t || a[i].v == s || a[i].v == t){ a[i].l = 1; }else { a[i].l = 0; } } mt19937 rd(time(0)); srand(rd()); random_shuffle(a + 1, a + m + 1); sort(a + 1, a + m + 1); for (int i = 1; i <= m; i++){ auto v = a[i]; int x = find(v.u), y = find(v.v); if (ans.size() < n - 1 && x != y && ((v.u == s && v.v != t && vs && (--vs || 1)) || (v.u == t && v.v != s && vt && (--vt || 1)) || (v.v == t && v.u != s && vt && (--vt || 1)) || (v.v == s && v.u != t && vs && (--vs || 1)) || (v.u == s && v.v == t && vs && vt && (--vs || 1) && (--vt || 1)) || (v.l == 0))){//十分优美,不是吗? fa[x] = y; ans.push_back({v.u, v.v}); } } if (ans.size() < n - 1){ cout << "No"; }else { cout << "Yes\n"; for (int i = 0; i < n - 1; i++){ cout << ans[i].f << ' ' << ans[i].s << '\n'; } } return 0; }

而且还是最优解呢喵~(Link

7|0CF1100E Andrew and Taxi

二分答案 x。如果一条边的边权 wi>x,那么就不能动。

然后拓扑排序。如果有环就说明 x 不可能达成。

然后如果 wi<x 并且 ui 在拓扑序中在 vi 的前面,那么这两条边就要互换。

然后输出答案即可。

#include<bits/stdc++.h> #define f first #define s second using namespace std; using pii = pair<int, vector<int>>; const int MAXN = 1e5 + 5; int n, m; vector<int> ans; struct Node{ int u, v, w; } a[MAXN]; pii check(int x){ vector<int> b[MAXN], ind(n + 1, 0), q(n + 1, 0); for (int i = 1; i <= m; i++){ if (a[i].w > x){ b[a[i].u].push_back(a[i].v); ind[a[i].v]++; } } queue<int> c; int cnt = 0; for (int i = 1; i <= n; i++){ if (!ind[i]){ c.push(i); } } while (c.size()){ auto p = c.front(); c.pop(); q[p] = ++cnt; for (auto v : b[p]){ ind[v]--; if (!ind[v]){ c.push(v); } } } vector<int> tmp; if (cnt != n){ return {0, tmp}; } for (int i = 1; i <= m; i++){ if (a[i].w <= x && q[a[i].u] > q[a[i].v]){ tmp.push_back(i); } } return {1, tmp}; } int main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> n >> m; for (int i = 1; i <= m; i++){ cin >> a[i].u >> a[i].v >> a[i].w; } int l = 0, r = 1e9 + 1; while (l < r){ int mid = (l + r) >> 1; auto p = check(mid); if (p.f){ ans = p.s; r = mid; }else { l = mid + 1; } } cout << l << ' ' << ans.size() << '\n'; for (auto v : ans){ cout << v << ' '; } return 0; }

8|0CF1200E Tourism

跑一遍拓扑,做 DP,然后特判就好了

#include<bits/stdc++.h> using namespace std; const int MAXN = 2e5 + 5; int n, m, a[MAXN], ind[MAXN]; long long ans, dp[MAXN], sum; vector<int> g[MAXN]; void topo(int u){ queue<int> b; for (int i = 1; i <= n; i++){ if (ind[i] == 1 && i != u){ b.push(i); } } while (b.size()){ auto x = b.front(); b.pop(); ind[x] = 0; for (auto v : g[x]){ dp[v] = max(dp[v], dp[x] + a[x]); if (!ind[v]){ continue; } if (v == u){ continue; } ind[v]--; if (ind[v] == 1){ b.push(v); } } } } int main (){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> n >> m; for (int i = 1; i <= n; i++){ cin >> a[i]; } while (m--){ int u, v; cin >> u >> v; g[u].push_back(v); g[v].push_back(u); ind[u]++, ind[v]++; } if (n == 1){ cout << a[1]; exit(0); }else if (n == 2){ cout << a[1] + a[2]; exit(0); } int s; cin >> s; topo(s); for (int i = 1; i <= n; i++){ if (ind[i]){ ans = max(ans, dp[i]); sum += a[i]; } } cout << ans + sum; return 0; }

9|0CF590C Three States

从所有国家出发跑一遍 BFS。记 disi,j,p(p{1,2,3}) 为点 (i,j) 到国家 p 的距离。

然后对于每个点求最小值即可。

#include<bits/stdc++.h> #define f first #define s second using namespace std; const int MAXN = 1e3 + 5; using pii = pair<int, int>; int n, m; long long dp[MAXN][MAXN][4]; string s[MAXN]; int fx[2][4] = {{1, -1, 0, 0}, {0, 0, 1, -1}}; deque<pii> b; void jl(int dx, int dy, pii x, int p){ if (dx >= 1 && dy >= 1 && dx <= n && dy <= m && s[dx][dy] != '#' && dp[x.f][x.s][p] + 1 < dp[dx][dy][p]){ dp[dx][dy][p] = dp[x.f][x.s][p] + (s[dx][dy] == '.'); if (s[dx][dy] == '.'){ b.push_back({dx, dy}); }else { b.push_front({dx, dy}); } } } void bfs(int p){ b.clear(); for (int i = 1; i <= n; i++){ for (int j = 1; j <= m; j++){ if (s[i][j] == p + '0'){ b.push_back({i, j}); dp[i][j][p] = 0; } } } while (b.size()){ auto x = b.front(); b.pop_front(); for (int i = 0; i < 4; i++){ int dx = x.f + fx[0][i], dy = x.s + fx[1][i]; jl(dx, dy, x, p); } } } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> n >> m; for (int i = 1; i <= n; i++){ cin >> s[i]; s[i] = ' ' + s[i]; for (int j = 1; j <= m; j++){ dp[i][j][1] = dp[i][j][2] = dp[i][j][3] = n * m; } } bfs(1), bfs(2), bfs(3); long long ans = 2e18; for (int i = 1; i <= n; i++){ for (int j = 1; j <= m; j++){ long long k = 1ll * dp[i][j][1] + dp[i][j][2] + dp[i][j][3]; if (s[i][j] == '.'){ k -= 2; } ans = min(ans, k); } } cout << (ans >= n * m ? -1 : ans); return 0; }

10|0CF48E Ivan the Fool VS Gorynych the Dragon

太菜了,不会。


__EOF__

本文作者tianbiandeshenghuo
本文链接https://www.cnblogs.com/tianbiandeshenghuo/p/20230921-tu-lun-zhuan-ti-test.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   天边的生活  阅读(31)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
加载中…

{{tag.name}}

{{tran.text}}{{tran.sub}}
无对应文字
有可能是
{{input}}
尚未录入,我来提交对应文字
点击右上角即可分享
微信分享提示