题解:P14606 [NWRRC 2025] Games of Chess
Build_Dreams · · 题解
脑子被吃了,题目读错一个小时。参考了 Priestess_SLG 的题解。
我们将问题转化为每一个颜色导出的图对应的每个点度数都是奇数。
对于
:::info[证明] 每个点的度数都是奇数,那么总度数也为奇数,然而在任意一个子图中,度数之和应为偶数,度数之和为偶数,矛盾。 :::
接下来我们猜测
尝试构造他,我们先尝试树,树我们可以对于每个叶子,如果父节点有偶数个儿子,那么将所有兄弟节点(包括自己)删掉并和父亲标记为同一种颜色,保留父亲,因为父亲现在连了偶数条边,还需连接奇数条边(这是原问题的子问题,于是直接保留父节点让父节点的父节点进行操作即可)否则如果父节点有奇数个儿子,直接将所有兄弟节点和父节点标记为同一种颜色并一道删除,因为它们的度数都已经符合条件了。
那么尝试推广,我们考虑 DFS 序。这个东西有“返祖边”的存在。
我们按照树的思路做,但是我们不好找“叶子”,换言之,我们不好动态维护叶子节点是哪些。
那么我们发现,
对于当前节点,还是从父亲节点入手。
如果父亲节点拥有奇数个儿子,那么这些节点和父节点直接一道删除并标记颜色即可。
否则,若拥有偶数个儿子,我们看看和之前的有什么不同。
注意到如果偶数个儿子有返祖边,我们设是
那么处理起来也很简单,我们将这个单独处理的话,剩下的兄弟节点数量就是奇数了,我们直接将这奇数个节点做常规处理,并将
否则没有返祖边,那就是常规处理即可。
代码很好写,但是也有细节。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int, int>
#define pll pair<ll, ll>
#define fi first
#define se second
bool begin_mem;
const int N = 2e5 + 5, M = 1e6 + 5;
const int inf = 1e9, mod = 998244353;
const ll INF = 1e18;
int n, m, fa[N], dep[N];
vector <int> g[N];
vector <int> tr[N];
vector <int> anc[N];
bool del[N], hav[N];
int poi[N];
void dfs(int f, int u){
dep[u] = dep[f] + 1, fa[u] = f;
for (auto v : g[u]){
if (v == f) continue;
if (!dep[v]) dfs(u, v), tr[u].push_back(v);
else if (dep[v] < dep[u]) anc[u].push_back(v), hav[u] = 1;
}
}
int p[N];
int find(int x){
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
void merge(int u, int v){
int pu = find(u), pv = find(v);
if (pu == pv) return ;
p[pu] = pv;
}
void init(){
}
void solve(){
cin >> n >> m;
for (int i = 1; i <= m; i ++ ){
int u, v; cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
if (n & 1) cout << "-1\n";
else{
dfs(0, 1);
priority_queue <pii> q;
for (int i = 1; i <= n; i ++ ) q.push((pii){dep[i], i}), p[i] = i;
for (int i = 1; i <= n; i ++ ) sort(anc[i].begin(), anc[i].end(), [&](int a, int b){
return dep[a] > dep[b];
});
while (q.size()){
auto [d, u] = q.top(); q.pop();
if (del[u] || dep[u] != d) continue;
int f = fa[u], hanc = -1;
if (!f) continue;
int tog = 0;
for (auto v : tr[f]){
if (del[v]) continue;
tog ++, hanc = (hav[v] ? v : hanc);
}
if (tog & 1){
for (auto v : tr[f]){
if (del[v]) continue;
del[v] = 1, merge(f, v);
}
del[f] = 1, merge(f, f);
}
else if (hanc == -1){
// 考虑这个情况和树一样
// 那么我们,把子树弄好然后这是偶数个挂给 f 了,那么我们其实可以直接删掉这些点
// 那么 f 处理完,正好所有数字都是合理的了
for (auto v : tr[f]){
if (del[v]) continue;
del[v] = 1, merge(f, v);
}
}
else{
// 这个时候就是说,我们的 hanc 这个节点要不被删,那么 hanc -> anc[hanc]
//int nf = anc[hanc].back(); anc[hanc].pop_back();
//int nf = anc[hanc].back(); anc[hanc].pop_back();
//while (anc[hanc].size() && del[anc[hanc].back()]) anc[hanc].pop_back();
//hav[hanc] = (anc[hanc].size() ? 1 : 0);
int nf = anc[hanc][poi[hanc]]; poi[hanc] ++ ;
hav[hanc] = (anc[hanc].size() > poi[hanc] ? 1 : 0);
// 然后我们考虑 hanc -> nf 这个操作
for (auto v : tr[f]){
if (del[v] || v == hanc) continue;
del[v] = 1, merge(f, v);
}
del[f] = 1, merge(f, f);
// 剩下的就是,将 hanc 挂到 nf 底下
// 这里是不是就是要改变 dep 了
// 那我们 dep 排序的操作?
// 注意到改变 dep 的时候 ndep != dep
// 那么我们好像 dep[u] != d continue 就好了
fa[hanc] = nf, dep[hanc] = dep[nf] + 1;
tr[nf].push_back(hanc);
//q.push({hanc, fa[hanc]});
q.push({dep[hanc], hanc});
}
}
vector <int> col(n + 1, 0); int omg = 0;
for (int i = 1; i <= n; i ++ ){
int qwq = find(i); assert(qwq != 0);
if (!col[qwq]) col[qwq] = ++ omg;
cout << col[qwq] << " ";
}
cout << "\n";
}
for (int i = 1; i <= n; i ++ ) g[i].clear(), tr[i].clear(), anc[i].clear();
for (int i = 1; i <= n; i ++ ) fa[i] = dep[i] = del[i] = p[i] = hav[i] = poi[i] = 0;
}
bool end_mem;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int T = 1;
cin >> T;
while (T -- ) init(), solve();
cerr << "Memory AwA " << fixed << setprecision(13) << abs(&begin_mem - &end_mem) / 1024.0 / 1024.0 << " Mb.\n";
cerr << "Memory AwA " << fixed << setprecision(13) << abs(&begin_mem - &end_mem) / 1024.0 / 1024.0 / 1024.0 << " Gb.\n";
}
/*
*
* 10:35
* 12:55
*
*
*/