题解:P11762 [IAMOI R1] 走亲访友

· · 题解

题意

给出一张 n 个点,m 条边的无向连通图,图中无自环、重边,初始位置为 s,每次沿边移动,移动后可以选择是否删除当前这条边。未删除的边可以多次移动,求构造 k 步移动内未删除边组成树的移动方案。

对于 100 \% 的数据,n \leq 10^3n - 1 \leq m \leq \frac{n(n - 1)}{2}k \leq m + n。保证有解。

Subtask 1、2 和 4

考虑先钦定一棵生成树,在树上移动并且删除剩下不在生成树上的边。容易发现遍历整棵树时,树上每条边最多经过 2 次,而对于每条多余的边,一去一回删除也最多经过 2 次,总移动步数最多为 2m 次,又有 m \leq \frac{n(n - 1)}{2},则移动步数严格小于 n^2,可通过 Subtask 1、2 和 4。

Subtask 3

其实用上面所述生成树的写法也可以。 ## Subtask 5 前面的 Subtask 其实和正解没有特别强的相关性。注意到如果这张图是一张欧拉图,那么肯定有一个显然的解法:还是钦定一棵生成树,在跑欧拉路径的时候判断若为树边则保留,否则删去。这样的最大移动步数为 $m$ 次。 于是考虑怎么把一张普通的图变成一张欧拉图。往图中加边,在 dfs 遍历生成树上节点的时候,判断该点度数是否为偶。若为奇则向父节点连边。可以证明最后会形成一个欧拉图。 对于增加的边,由于原先向父节点的连边为树边,所以保证这条边不会被删除。根节点不用向上连边,最多增加边数为 $n - 1$,总移动次数严格小于 $n + m$。 实现细节上要注意在 dfs 遍历的同时不要加边。这是未定义行为。可能会导致 RE 或 WA。 ::::info[如何证明“最后一定会形成一个欧拉图”?] 简单意会即可。欧拉图的总度数必为偶数,保证了根节点以下的度数均为偶,那么根节点的度数也必然为偶。 :::: ::::success[代码] dfs 的时候可以直接生成树,我也不知道当时为什么写了个并查集。 ```cpp #include <iostream> #include <vector> #include <queue> #include <algorithm> #include <stack> #include <cstring> #include <cstdio> using namespace std; using i64 = long long; using u64 = unsigned long long; using pii = pair<int, int>; #define FILENAME "path" const int N = 3e6 + 10; const i64 P = 1e9 + 7; struct node { int v, tag, ID; node() : v(0), tag(0), ID(0) {} node(int v, int tag, int ID) : v(v), tag(tag), ID(ID) {} }; int n, m, k, s; int deg[N], fa[N], etag[N]; // etag:每条边对应的 tag vector <node> G[N]; int rel[N], vis[N]; // rel:新建边对应的旧边编号 int Find(int x) { return (x == fa[x]) ? x : fa[x] = Find(fa[x]); } // 构造欧拉图 vector <node> V[N]; void dfs1(int u, int f, int fid) { for(auto [v, tag, ID] : G[u]) { if(v == f) continue; if(tag == 1) dfs1(v, u, ID); } if((deg[u] & 1) && u != s) { m++; G[u].emplace_back(node(f, 2, m)); V[f].emplace_back(node(u, 2, m)); // 不要在遍历时加边 rel[m] = fid, etag[m] = 2, deg[u]++, deg[f]++; } return void(); } // 无向图欧拉路径 vector <pii> ans; int now[N]; void dfs2(int u, int fid) { if(vis[fid]) return void(); vis[fid] = 1; for(; now[u] < G[u].size();) { auto [v, tag, ID] = G[u][now[u]++]; dfs2(v, ID); } if(fid) ans.emplace_back(make_pair(fid, etag[fid])); return void(); } void solve() { cin >> n >> m >> k >> s; int rm = m; for(int i = 1; i <= n; i++) fa[i] = i; for(int i = 1; i <= m; i++) { int u, v; cin >> u >> v; int uu = Find(u), vv = Find(v); if(uu == vv) { etag[i] = 0; G[u].emplace_back(node(v, 0, i)); G[v].emplace_back(node(u, 0, i)); } else { etag[i] = 1; G[u].emplace_back(node(v, 1, i)); G[v].emplace_back(node(u, 1, i)); fa[uu] = vv; } deg[u]++, deg[v]++; } dfs1(s, s, 0); for(int i = 1; i <= n; i++) { for(auto k : V[i]) G[i].push_back(k); } dfs2(s, 0); reverse(ans.begin(), ans.end()); cout << ans.size() << '\n'; for(auto [i, j] : ans) cout << (i > rm ? rel[i] : i) << ' ' << (j ? 1 : 0) << '\n'; return void(); } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); #ifdef FILENAME freopen(FILENAME ".in", "r", stdin); freopen(FILENAME ".out", "w", stdout); #endif solve(); return 0; } ``` ::::