Description
Kevin 有一个包含 \(n\) 个顶点和 \(m\) 条边的无向图。初始时,某些顶点上有石子,Kevin 想要将这些石子移动到新位置。
Kevin 可以执行以下操作:
- 对于每个位于 \(u_i\) 的石子,选择一个相邻顶点 \(v_i\)。同时将所有石子从各自的 \(u_i\) 移动到对应的 \(v_i\)。
在任何时刻,每个顶点最多只能包含一个石子。
请判断是否存在有效的操作序列可以将石子从初始状态移动到目标状态。若存在,请输出不超过 \(2n\) 步的有效操作序列。可以证明,如果存在有效序列,则必然存在步数不超过 \(2n\) 的有效序列。
无重边和自环。
\(n\leq 2000,m\leq 10^4\)。
Solution
首先考虑怎么判定。
容易想到下面几个必要的条件:
- 初始和结束状态都能动至少一步。
- 所有连通块初始和结束状态的石子数相同。
- 如果一个连通块是二分图,则这个连通块 \(s_0=t_0,s_1=t_1\) 或者 \(s_0=t_1,s_1=t_0\)。
- 任意两个二分图连通块通过条件 2 推出的操作总步数的奇偶性不能有矛盾。
要想到这个东西应该是先发现操作可逆,所以初始和结束状态都必须能动,且操作次数和奇偶性有很大的关系。如果一个连通块是二分图,则每次操作都会让每个点变换颜色,容易推出 3,4 结论。否则的话可以想到每个连通块能够一直动下去。
严谨证明就考虑直接构造吧。
第一个条件可以通过跑二分图最大匹配来判断,即对每个点拆成 \(u_0,u_1\),对于一条原图的边 \((u,v)\),在新图上连 \(u_0\to v_1\) 和 \(u_1\to v_0\),然后判断对于初始和结束的状态的每个点是否都能找到匹配。
注意到这样拆点的话 \(u_0\) 初始状态走偶数步走到 \(u\),\(u_1\) 是初始状态走奇数步到 \(u\),所以如果知道了总步数的奇偶性后,把最终状态钦定到对应的左/右部点,走的过程就等价于在这个二分图上走了,就不用考虑有奇环的情况了!
但是现在图的形态还是不够漂亮,考虑做一些操作让图变得更简单一点。
套路性地把两次二分图最大匹配的匹配边拿出来,并只保留这些边,则整个图会划分成若干个偶环或者链。
由于一个偶环一定是由两种匹配边交替构成,把一种匹配边全部替换换成另一种一定也合法,对所有偶环替换后整个图就变成若干条链了,再把所有目前没有选择的边依次加入,就能构成一个森林了。
容易发现这个森林的判定条件和原二分图是等价的,所以只需要在这个森林上构造。
考虑每次找到整个森林上两种匹配边中最靠“外”的一条边,假设它是最终状态的匹配边。如果是初始状态的边,由于操作可逆,是一样的。
然后找到离这条边最近的初始边,让这个初始边上的点走到钦定的最终状态的边,其余的边在这个过程中一直在自己的匹配边上来回走。
等到最外面的边归位后就能删掉,再做同样的过程。总次数是 \(O(n^2)\) 的,过不了。
注意到这么做非常浪费,考虑让走的过程并行进行。
由于选的是最近的点,所以让这个点先走两步,其余的点来回走两步,再做剩下的过程是不会相撞的。因为如果相撞,由于已经让别的点走了两步来拖延时间,相撞就能推出选的不是最近的点,也就矛盾了。
实现上述做法整个过程就是线性的了,直观上会发现上面最劣应该是 \(2n+2n=4n\) 的,但是前面走到终点的点不能再被经过,也就让第 \(k\) 个出发的点的时间变为 \(n-k+2k=n+k\),一定不超过 \(2n\)。
时间复杂度:\(O(n^2+nm)\)。
Code
#include <bits/stdc++.h>// #define int int64_tconst int kMaxN = 4e3 + 5, kMaxM = 1e4 + 5;int n, m, t, r;
int u[kMaxM], v[kMaxM], id1[kMaxN], id2[kMaxN];
int dis[kMaxN][kMaxN], match1[kMaxN], match2[kMaxN], dep[kMaxN];
int rev1[kMaxN], rev2[kMaxN];
bool have1[kMaxN], have2[kMaxN], del1[kMaxN], del2[kMaxN];
std::string str1, str2;
std::vector<int> rt, G[kMaxN], T[kMaxN], res[kMaxN];void clear() {t = r = 0;rt.clear();for (int i = 0; i <= 2 * n; ++i) G[i].clear(), T[i].clear(), res[i].clear(), have1[i] = have2[i] = del1[i] = del2[i] = 0;
}namespace Bipartite {
int cnt1[2], cnt2[2], col[kMaxN];
bool bip;void dfs(int u) {cnt1[col[u]] += str1[u - 1] - '0';cnt2[col[u]] += str2[u - 1] - '0';for (auto v : G[u]) {if (col[v] == -1) {col[v] = col[u] ^ 1;dfs(v);} else if (col[v] != col[u] ^ 1) {bip = 0;}}
}int check() {int r = -2;std::fill_n(col + 1, n, -1);for (int i = 1; i <= n; ++i) {if (col[i] != -1) continue;cnt1[0] = cnt1[1] = cnt2[0] = cnt2[1] = col[i] = 0;bip = 1, dfs(i);if (cnt1[0] + cnt1[1] != cnt2[0] + cnt2[1]) return -1;if (bip) {bool fl0 = (cnt1[0] == cnt2[0]), fl1 = (cnt1[0] == cnt2[1]);if (!fl0 && !fl1) {return -1;} else if (fl0 && !fl1) {if (r == 1) return -1;r = 0;} else if (!fl0 && fl1) {if (r == 0) return -1;r = 1;}}}if (r == -2) r = 0;return r;
}
} // namespace Bipartitenamespace Hungary {
bool vis[kMaxN];bool dfs(int u, int *match) {for (auto v : G[u]) {if (vis[v]) continue;vis[v] = 1;if (!match[v] || dfs(match[v], match)) {match[v] = u;return 1;}}return 0;
}int getmatch1() {for (int i = 1; i <= n; ++i) match1[i] = vis[i] = 0;int cnt = 0;for (int i = 1; i <= t; ++i) {std::fill_n(vis + 1, n, 0);cnt += dfs(id1[i], match1);}return cnt;
}int getmatch2() {for (int i = 1; i <= n; ++i) match2[i] = vis[i] = 0;int cnt = 0;for (int i = 1; i <= t; ++i) {std::fill_n(vis + 1, n, 0);cnt += dfs(id2[i], match2);}return cnt;
}
} // namespace Hungaryvoid fixring() {static std::vector<std::pair<int, int>> G[kMaxN];static bool vis[kMaxN];for (int i = 1; i <= 2 * n; ++i) G[i].clear(), vis[i] = 0;for (int i = 1; i <= n; ++i) {if (match1[i]) G[match1[i]].emplace_back(i + n, 0), G[i + n].emplace_back(match1[i], 0);if (match2[i]) {if (!r) G[match2[i]].emplace_back(i + n, 1), G[i + n].emplace_back(match2[i], 1);else G[i].emplace_back(match2[i] + n, 1), G[match2[i] + n].emplace_back(i, 1);}}for (int i = 1; i <= 2 * n; ++i) {if (vis[i]) continue;std::vector<int> id;std::function<void(int)> dfs = [&] (int u) {vis[u] = 1;id.emplace_back(u);for (auto [v, o] : G[u]) {if (vis[v]) continue;dfs(v);}};dfs(i);bool isring = 1;for (auto u : id) isring &= (G[u].size() == 2);if (!isring) continue;assert(id.size() % 2 == 0);for (int j = 0; j < id.size(); ++j) {if (id[j] <= n) continue;int u = id[j] - n;if (match1[u] == id[(j + id.size() - 1) % id.size()]) match1[u] = id[(j + 1) % id.size()];else if (match1[u] == id[(j + 1) % id.size()]) match1[u] = id[(j + id.size() - 1) % id.size()];}}
}struct DSU {int fa[kMaxN];void init(int n) { std::iota(fa + 1, fa + 1 + n, 1); }int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }bool unionn(int x, int y) {int fx = find(x), fy = find(y);if (fx != fy) return fa[fx] = fy, 1;else return 0;}
} dsu;void build() {static bool vis[kMaxN][kMaxN];dsu.init(2 * n);for (int i = 1; i <= n; ++i) std::fill_n(vis[i] + 1, n, 0);for (int i = 1; i <= n; ++i) {if (match1[i] && !vis[match1[i]][i]) {assert(dsu.unionn(match1[i], i + n));vis[match1[i]][i] = 1;T[match1[i]].emplace_back(i + n), T[i + n].emplace_back(match1[i]);}if (!r) {if (match2[i] && !vis[match2[i]][i]) {assert(dsu.unionn(match2[i], i + n));vis[match2[i]][i] = 1;T[match2[i]].emplace_back(i + n), T[i + n].emplace_back(match2[i]);}} else {if (match2[i] && !vis[i][match2[i]]) {assert(dsu.unionn(i, match2[i] + n));vis[i][match2[i]] = 1;T[i].emplace_back(match2[i] + n), T[match2[i] + n].emplace_back(i);}}}for (int i = 1; i <= m; ++i) {if (dsu.unionn(u[i], v[i] + n)) {T[u[i]].emplace_back(v[i] + n), T[v[i] + n].emplace_back(u[i]);}if (dsu.unionn(v[i], u[i] + n)) {T[v[i]].emplace_back(u[i] + n), T[u[i] + n].emplace_back(v[i]);}}
}void getrt() {std::fill_n(dep + 1, 2 * n, 0);std::function<void(int, int)> dfs = [&] (int u, int fa) {dep[u] = dep[fa] + 1;for (auto v : T[u]) {if (v == fa) continue;dfs(v, u);}};for (int i = 1; i <= 2 * n; ++i) {if (!dep[i]) dfs(i, 0);}
}void getdis() {for (int i = 1; i <= 2 * n; ++i) {std::fill_n(dis[i] + 1, 2 * n, 1e9);std::function<void(int, int)> dfs = [&] (int u, int fa) {dis[i][u] = dis[i][fa] + 1;for (auto v : T[u]) {if (v == fa) continue;dfs(v, u);}};dis[i][0] = -1;dfs(i, 0);}
}std::vector<int> getpath(int x, int y) {std::vector<int> ret, vec;std::function<void(int, int)> dfs = [&] (int u, int fa) {vec.emplace_back(u);if (u == y) ret = vec;for (auto v : T[u]) {if (v == fa) continue;dfs(v, u);}vec.pop_back();};dfs(x, 0);assert(ret.size());return ret;
}void dickdreamer() {std::cin >> n >> m >> str1 >> str2;clear();for (int i = 1; i <= m; ++i) std::cin >> u[i] >> v[i];if (std::count(str1.begin(), str1.end(), '1') != std::count(str2.begin(), str2.end(), '1'))return void(std::cout << "No\n");t = std::count(str1.begin(), str1.end(), '1');for (int i = 1, t1 = 0, t2 = 0; i <= n; ++i) {if (str1[i - 1] == '1') id1[++t1] = i;if (str2[i - 1] == '1') id2[++t2] = i;}if (str1 == str2) {std::cout << "Yes\n0\n";for (int i = 1; i <= t; ++i) std::cout << id1[i] << " \n"[i == t];return;}for (int i = 1; i <= m; ++i) G[u[i]].emplace_back(v[i]), G[v[i]].emplace_back(u[i]);if (Hungary::getmatch1() != t || Hungary::getmatch2() != t) return void(std::cout << "No\n");r = Bipartite::check();if (r == -1) return void(std::cout << "No\n");fixring(), build();for (int i = 1; i <= t; ++i) {for (int j = 1; j <= n; ++j) {if (match1[j] == id1[i]) rev1[i] = j + n;if (match2[j] == id2[i]) rev2[i] = j + (r ^ 1) * n;}}for (int i = 1; i <= t; ++i) have1[id1[i]] = have2[id2[i] + r * n] = 1;getrt(), getdis();int L = 0, R = 2 * n - r;for (int c = 1; c <= t; ++c) {int id = 0, op = -1, mx = -1;for (int i = 1; i <= t; ++i) {if (!del1[i] && std::max(dep[id1[i]], dep[rev1[i]]) > mx) id = i, op = 0, mx = std::max(dep[id1[i]], dep[rev1[i]]);if (!del2[i] && std::max(dep[id2[i] + r * n], dep[rev2[i]]) > mx) id = i, op = 1, mx = std::max(dep[id2[i] + r * n], dep[rev2[i]]);}assert(op != -1);if (op == 0) {int _id = 0, mi = 1e9;for (int i = 1; i <= t; ++i) {if (!del2[i] && std::min(dis[id1[id]][id2[i] + r * n], dis[id1[id]][rev2[i]]) < mi)_id = i, mi = std::min(dis[id1[id]][id2[i] + r * n], dis[id1[id]][rev2[i]]);}assert(_id);del1[id] = del2[_id] = 1;auto path = getpath(id2[_id] + r * n, id1[id]);for (int i = 0; i <= 2 * n - r; ++i) {if (i > R) res[i].emplace_back((2 * n - r - i) % 2 == 0 ? id2[_id] + r * n : rev2[_id]);else if (i > R - path.size()) res[i].emplace_back(path[R - i]);else res[i].emplace_back(i % 2 == 0 ? id1[id] : rev1[id]);}R -= 2;} else {int _id = 0, mi = 1e9;for (int i = 1; i <= t; ++i) {if (!del1[i] && std::min(dis[id2[id] + r * n][id1[i]], dis[id2[id] + r * n][rev1[i]]) < mi)_id = i, mi = std::min(dis[id2[id] + r * n][id1[i]], dis[id2[id] + r * n][rev1[i]]);}assert(_id);del2[id] = del1[_id] = 1;auto path = getpath(id1[_id], id2[id] + r * n);for (int i = 0; i <= 2 * n - r; ++i) {if (i < L) res[i].emplace_back(i % 2 == 0 ? id1[_id] : rev1[_id]);else if (i < L + path.size()) res[i].emplace_back(path[i - L]);else res[i].emplace_back((2 * n - r - i) % 2 == 0 ? id2[id] + r * n : rev2[id]);}L += 2;}}std::cout << "Yes\n" << 2 * n - r << '\n';for (int i = 0; i <= 2 * n - r; ++i) {for (auto x : res[i]) std::cout << (x <= n ? x : x - n) << ' ';std::cout << '\n';}
}int32_t main() {
#ifdef ORZXKRfreopen("in.txt", "r", stdin);freopen("out.txt", "w", stdout);
#endifstd::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);int T = 1;std::cin >> T;while (T--) dickdreamer();// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";return 0;
}