結果
| 問題 | No.3769 Even Contraction |
| コンテスト | |
| ユーザー |
|
| 提出日時 | 2026-10-04 20:30:14 |
| 言語 | C++17 (gcc 15.3.0 + boost 1.92.0 + ACL) |
| 結果 |
WA
不安定
|
| 実行時間 | - |
| コード長 | 27,274 bytes |
| 記録 | |
| コンパイル時間 | 3,141 ms |
| コンパイル使用メモリ | 286,672 KB |
| 実行使用メモリ | 22,528 KB |
| 最終ジャッジ日時 | 2026-10-16 20:56:24 |
| 合計ジャッジ時間 | 8,594 ms |
|
ジャッジサーバーID (参考情報) |
judge5_0 / judge1_0 |
(要ログイン)
| ファイルパターン | 結果 |
|---|---|
| sample | AC * 3 |
| other | AC * 31 WA * 16 |
ソースコード
#include <bits/stdc++.h>
using namespace std;
const int NEG = -1e9;
struct Input {
int n;
vector<vector<int>> g;
vector<int> a;
};
Input read_input() {
Input in;
cin >> in.n;
in.g.assign(in.n, {});
for (int i = 0; i < in.n - 1; i++) {
int u, v;
cin >> u >> v;
--u, --v;
in.g[u].push_back(v);
in.g[v].push_back(u);
}
in.a.resize(in.n);
for (int& x : in.a) cin >> x;
return in;
}
struct DSU {
vector<int> p, sz;
DSU(int n = 0) : p(n, -1), sz(n, 1) {}
int root(int x) {
return p[x] < 0 ? x : p[x] = root(p[x]);
}
bool unite(int a, int b) {
a = root(a);
b = root(b);
if (a == b) return false;
if (sz[a] < sz[b]) swap(a, b);
p[b] = a;
sz[a] += sz[b];
return true;
}
};
bool legal(int x, int y) {
return (x & 1) == (y & 1);
}
bool is02(int x, int y) {
return (x == 0 && y == 2) || (x == 2 && y == 0);
}
// ============================================================
// 動的に木を縮約するための雑なヘルパー
// ============================================================
struct DynTree {
int n;
vector<unordered_set<int>> adj;
vector<int> val;
vector<char> alive;
DynTree(const Input& in)
: n(in.n), adj(n), val(in.a), alive(n, 1) {
for (int v = 0; v < n; v++) {
for (int u : in.g[v]) adj[v].insert(u);
}
}
bool connected(int u, int v) const {
return 0 <= u && u < n &&
0 <= v && v < n &&
alive[u] && alive[v] &&
adj[u].count(v);
}
int merge_edge(int u, int v) {
int nv = (val[u] + val[v]) / 2;
adj[u].erase(v);
adj[v].erase(u);
// small-to-large
if (adj[u].size() < adj[v].size()) swap(u, v);
vector<int> mv(adj[v].begin(), adj[v].end());
for (int w : mv) {
adj[w].erase(v);
adj[w].insert(u);
adj[u].insert(w);
}
adj[v].clear();
alive[v] = false;
val[u] = nv;
return u;
}
};
int run_phase(
DynTree& d,
function<bool(int, int)> ok
) {
queue<pair<int, int>> q;
for (int u = 0; u < d.n; u++) {
if (!d.alive[u]) continue;
for (int v : d.adj[u]) {
if (u < v) q.push({u, v});
}
}
int got = 0;
while (!q.empty()) {
auto [u, v] = q.front();
q.pop();
if (!d.connected(u, v)) continue;
if (!ok(d.val[u], d.val[v])) continue;
int w = d.merge_edge(u, v);
got++;
for (int z : d.adj[w]) {
q.push({w, z});
}
}
return got;
}
// ============================================================
// 1.
// 「同じ値の辺は全部先に縮約して損しない」
// → その後は適当に合法辺を縮約
//
// 0-2-2-0 などで死亡
// ============================================================
int wrong1(const Input& in) {
DynTree d(in);
int ans = 0;
ans += run_phase(
d,
[](int x, int y) {
return x == y;
}
);
ans += run_phase(
d,
[](int x, int y) {
return legal(x, y);
}
);
return ans;
}
// ============================================================
// 2.
// 完全にフェーズ固定
//
// 同値縮約
// → 0-2
// → 1-1
//
// という順番だけで処理
// ============================================================
int wrong2(const Input& in) {
DynTree d(in);
int ans = 0;
ans += run_phase(
d,
[](int x, int y) {
return x == y;
}
);
ans += run_phase(
d,
[](int x, int y) {
return is02(x, y);
}
);
ans += run_phase(
d,
[](int x, int y) {
return x == 1 && y == 1;
}
);
return ans;
}
// ============================================================
// 3.
// 「木なら葉から貪欲でよい」
//
// 縮約可能な葉辺だけをどんどん縮約する。
// 1-0-2-0 などで死亡。
// ============================================================
int wrong3(const Input& in) {
DynTree d(in);
int ans = 0;
queue<int> q;
for (int v = 0; v < d.n; v++) {
if (d.adj[v].size() == 1) q.push(v);
}
while (!q.empty()) {
int v = q.front();
q.pop();
if (!d.alive[v]) continue;
if (d.adj[v].size() != 1) continue;
int u = *d.adj[v].begin();
if (!legal(d.val[v], d.val[u])) continue;
int w = d.merge_edge(v, u);
ans++;
if (d.adj[w].size() == 1) {
q.push(w);
}
for (int z : d.adj[w]) {
if (d.adj[z].size() == 1) {
q.push(z);
}
}
}
return ans;
}
// ============================================================
// 4.
// 「0-2 -> 1 が特殊だから、とにかく 0-2 を優先」
//
// 0-2 が存在する限り最優先。
// なければ他の合法辺。
// ============================================================
int wrong4(const Input& in) {
DynTree d(in);
int ans = 0;
queue<pair<int, int>> q02;
queue<pair<int, int>> qo;
auto push_edge = [&](int u, int v) {
if (!d.connected(u, v)) return;
int x = d.val[u];
int y = d.val[v];
if (!legal(x, y)) return;
if (is02(x, y)) {
q02.push({u, v});
} else {
qo.push({u, v});
}
};
for (int u = 0; u < d.n; u++) {
for (int v : d.adj[u]) {
if (u < v) push_edge(u, v);
}
}
while (true) {
pair<int, int> e = {-1, -1};
while (!q02.empty()) {
auto [u, v] = q02.front();
q02.pop();
if (
d.connected(u, v) &&
is02(d.val[u], d.val[v])
) {
e = {u, v};
break;
}
}
if (e.first == -1) {
while (!qo.empty()) {
auto [u, v] = qo.front();
qo.pop();
if (
d.connected(u, v) &&
legal(d.val[u], d.val[v]) &&
!is02(d.val[u], d.val[v])
) {
e = {u, v};
break;
}
}
}
if (e.first == -1) break;
int w = d.merge_edge(e.first, e.second);
ans++;
for (int z : d.adj[w]) {
push_edge(w, z);
}
}
return ans;
}
// ============================================================
// 0-2 辺だけからなる maximum matching
// ============================================================
int matching02(const Input& in) {
int n = in.n;
// dp[v][0]:
// v は親とはマッチしていない
//
// dp[v][1]:
// v は親とマッチ済み
vector<array<int, 2>> dp(n);
function<void(int, int)> dfs =
[&](int v, int p) {
int base = 0;
for (int u : in.g[v]) {
if (u == p) continue;
dfs(u, v);
base += dp[u][0];
}
dp[v][1] = base;
dp[v][0] = base;
for (int u : in.g[v]) {
if (u == p) continue;
if (!is02(in.a[v], in.a[u])) continue;
dp[v][0] = max(
dp[v][0],
base
- dp[u][0]
+ dp[u][1]
+ 1
);
}
};
dfs(0, -1);
return dp[0][0];
}
// ============================================================
// 5.
// 「同値辺は取る +
// 0-2 辺は maximum matching」
//
// 1-0-2 など、生成した1の価値を評価できず死亡。
// ============================================================
int wrong5(const Input& in) {
int same = 0;
for (int v = 0; v < in.n; v++) {
for (int u : in.g[v]) {
if (v < u && in.a[v] == in.a[u]) {
same++;
}
}
}
return min(
in.n - 1,
same + matching02(in)
);
}
// ============================================================
// 6.
// 「0/2 の連結成分に 0 と 2 が両方存在すれば、
// その成分全体を1にできる」
//
// 星型などで死亡。
// ============================================================
int wrong6(const Input& in) {
int n = in.n;
DSU uf(n);
// 1 を除いた 0/2 成分
for (int v = 0; v < n; v++) {
for (int u : in.g[v]) {
if (v >= u) continue;
if (
in.a[v] != 1 &&
in.a[u] != 1
) {
uf.unite(v, u);
}
}
}
vector<int> sz(n);
vector<int> has0(n);
vector<int> has2(n);
for (int v = 0; v < n; v++) {
if (in.a[v] == 1) continue;
int r = uf.root(v);
sz[r]++;
has0[r] |= (in.a[v] == 0);
has2[r] |= (in.a[v] == 2);
}
int ans = 0;
// 0/2成分内部は全部縮約可能だと決めつける
for (int r = 0; r < n; r++) {
if (sz[r]) {
ans += sz[r] - 1;
}
}
// mixed な成分は「1になれる」とみなす
for (int v = 0; v < n; v++) {
for (int u : in.g[v]) {
if (v >= u) continue;
if (in.a[v] == 1 && in.a[u] == 1) {
ans++;
}
else if (
in.a[v] == 1 &&
in.a[u] != 1
) {
int r = uf.root(u);
if (has0[r] && has2[r]) {
ans++;
}
}
else if (
in.a[u] == 1 &&
in.a[v] != 1
) {
int r = uf.root(v);
if (has0[r] && has2[r]) {
ans++;
}
}
}
}
return min(n - 1, ans);
}
// ============================================================
// 同値連結成分を縮約
// ============================================================
struct CompTree {
int m;
int internal;
vector<vector<int>> g;
vector<int> val;
vector<int> sz;
};
CompTree compress_equal(const Input& in) {
int n = in.n;
DSU uf(n);
for (int v = 0; v < n; v++) {
for (int u : in.g[v]) {
if (
v < u &&
in.a[v] == in.a[u]
) {
uf.unite(v, u);
}
}
}
vector<int> id(n, -1);
int m = 0;
for (int v = 0; v < n; v++) {
if (uf.root(v) == v) {
id[v] = m++;
}
}
vector<int> rid(n);
for (int v = 0; v < n; v++) {
rid[v] = id[uf.root(v)];
}
CompTree ct;
ct.m = m;
ct.internal = n - m;
ct.g.assign(m, {});
ct.val.assign(m, 0);
ct.sz.assign(m, 0);
for (int v = 0; v < n; v++) {
int c = rid[v];
ct.val[c] = in.a[v];
ct.sz[c]++;
}
for (int v = 0; v < n; v++) {
for (int u : in.g[v]) {
if (v >= u) continue;
if (rid[v] == rid[u]) continue;
ct.g[rid[v]].push_back(rid[u]);
ct.g[rid[u]].push_back(rid[v]);
}
}
return ct;
}
// ============================================================
// 7.
// 「同値成分を1頂点に完全縮約してから
// 4状態DPすればよい」
//
// port情報を失う。
// 0-0 の異なる頂点に 2 が付く場合などを区別できない。
// ============================================================
int wrong7(const Input& in) {
auto ct = compress_equal(in);
int m = ct.m;
vector<array<int, 4>> dp(m);
function<void(int, int)> dfs =
[&](int v, int p) {
// 0: 非1
// 1: 将来1
// 2: 1かつ余裕あり
// 3: 1かつ余裕なし
array<int, 4> cur = {
NEG, NEG, NEG, NEG
};
if (ct.val[v] == 1) {
cur[2] = 0;
} else {
cur[0] = 0;
cur[1] = 0;
}
for (int u : ct.g[v]) {
if (u == p) continue;
dfs(u, v);
array<int, 4> nxt = {
NEG, NEG, NEG, NEG
};
for (int k = 0; k < 4; k++) {
if (cur[k] <= NEG / 2) continue;
for (int j = 0; j < 4; j++) {
if (dp[u][j] <= NEG / 2) continue;
// 辺を使わない
// 子の予約状態はここでは残せない
if (j != 1) {
nxt[k] = max(
nxt[k],
cur[k] + dp[u][j]
);
}
// 両方1なら 1-1 として縮約
if (
k != 0 &&
j != 0 &&
k != 1 &&
j != 1
) {
nxt[k] = max(
nxt[k],
cur[k] + dp[u][j] + 1
);
}
// 0-2 を起爆として使用
if (
is02(ct.val[v], ct.val[u]) &&
k != 0 &&
j != 0 &&
k != 3 &&
j != 3
) {
nxt[3] = max(
nxt[3],
cur[k] + dp[u][j] + 1
);
}
}
}
cur = nxt;
}
dp[v] = cur;
};
dfs(0, -1);
int best = max({
dp[0][0],
dp[0][2],
dp[0][3]
});
return ct.internal + best;
}
// ============================================================
// 8.
// dp[v][0/1/2]
//
// 「部分木を完全に処理した後の
// vを含む塊の最終値だけ持てばよい」
//
// 0-0-2 などで予約ができず死亡。
// ============================================================
int wrong8(const Input& in) {
int n = in.n;
vector<array<int, 3>> dp(n);
function<void(int, int)> dfs =
[&](int v, int p) {
array<int, 3> cur = {
NEG, NEG, NEG
};
cur[in.a[v]] = 0;
for (int u : in.g[v]) {
if (u == p) continue;
dfs(u, v);
array<int, 3> nxt = {
NEG, NEG, NEG
};
for (int x = 0; x < 3; x++) {
if (cur[x] <= NEG / 2) continue;
for (int y = 0; y < 3; y++) {
if (dp[u][y] <= NEG / 2) continue;
// 親子辺を残す
nxt[x] = max(
nxt[x],
cur[x] + dp[u][y]
);
// 今ここで縮約
if ((x & 1) == (y & 1)) {
int z = (x + y) / 2;
nxt[z] = max(
nxt[z],
cur[x] + dp[u][y] + 1
);
}
}
}
cur = nxt;
}
dp[v] = cur;
};
dfs(0, -1);
return max({
dp[0][0],
dp[0][1],
dp[0][2]
});
}
// ============================================================
// 9.
// さらに状態を潰して
//
// 0: 元の非1色のまま
// 1: 1になれる
//
// だけでDP。
// 「余裕あり1 / 余裕なし1 / 予約」を区別しない。
// ============================================================
int wrong9(const Input& in) {
int n = in.n;
vector<array<int, 2>> dp(n);
function<void(int, int)> dfs =
[&](int v, int p) {
array<int, 2> cur = {
NEG, NEG
};
if (in.a[v] == 1) {
cur[1] = 0;
} else {
cur[0] = 0;
}
for (int u : in.g[v]) {
if (u == p) continue;
dfs(u, v);
array<int, 2> nxt = {
NEG, NEG
};
for (int s = 0; s < 2; s++) {
if (cur[s] <= NEG / 2) continue;
for (int t = 0; t < 2; t++) {
if (dp[u][t] <= NEG / 2) continue;
int x = s ? 1 : in.a[v];
int y = t ? 1 : in.a[u];
// 辺を使わない
nxt[s] = max(
nxt[s],
cur[s] + dp[u][t]
);
if ((x & 1) == (y & 1)) {
int z = (x + y) / 2;
int ns = (z == 1);
if (
z == 1 ||
z == in.a[v]
) {
nxt[ns] = max(
nxt[ns],
cur[s] + dp[u][t] + 1
);
}
}
}
}
cur = nxt;
}
dp[v] = cur;
};
dfs(0, -1);
return max(
dp[0][0],
dp[0][1]
);
}
// ============================================================
// 10.
// 「初期値1で木を分割し、
// 各 0/2 成分を独立最適化」
//
// mixed成分なら隣接する1全部につながる、と考える。
// ============================================================
int wrong10(const Input& in) {
int n = in.n;
DSU uf(n);
for (int v = 0; v < n; v++) {
for (int u : in.g[v]) {
if (v >= u) continue;
if (
in.a[v] != 1 &&
in.a[u] != 1
) {
uf.unite(v, u);
}
}
}
vector<int> has0(n);
vector<int> has2(n);
for (int v = 0; v < n; v++) {
if (in.a[v] == 1) continue;
int r = uf.root(v);
has0[r] |= (in.a[v] == 0);
has2[r] |= (in.a[v] == 2);
}
int ans = matching02(in);
// 同値辺は全部安全だと仮定
for (int v = 0; v < n; v++) {
for (int u : in.g[v]) {
if (
v < u &&
in.a[v] == in.a[u]
) {
ans++;
}
}
}
// mixed component は隣の1全部と接続可能と仮定
for (int v = 0; v < n; v++) {
for (int u : in.g[v]) {
if (v >= u) continue;
if (
in.a[v] == 1 &&
in.a[u] != 1
) {
int r = uf.root(u);
if (has0[r] && has2[r]) {
ans++;
}
}
if (
in.a[u] == 1 &&
in.a[v] != 1
) {
int r = uf.root(v);
if (has0[r] && has2[r]) {
ans++;
}
}
}
}
return min(n - 1, ans);
}
// ============================================================
// 11.
// 木構造を無視。
// #0, #1, #2 だけで答えを計算。
// ============================================================
int wrong11(const Input& in) {
int c[3] = {};
for (int x : in.a) {
c[x]++;
}
int k = min(c[0], c[2]);
// 0+2 を k 回
int ans = k;
// 作った1と元の1を全部連結できると仮定
int ones = c[1] + k;
if (ones) {
ans += ones - 1;
}
// 残った0同士・2同士も全部連結できると仮定
int r0 = c[0] - k;
int r2 = c[2] - k;
if (r0) ans += r0 - 1;
if (r2) ans += r2 - 1;
return min(in.n - 1, ans);
}
// ============================================================
// 12.
// 0/2領域について
//
// 「0の同値成分数」と
// 「2の同値成分数」の差
//
// だけで残る個数が決まる、と仮定。
// ============================================================
int wrong12(const Input& in) {
int n = in.n;
DSU mono(n);
DSU region(n);
for (int v = 0; v < n; v++) {
for (int u : in.g[v]) {
if (v >= u) continue;
if (
in.a[v] != 1 &&
in.a[u] != 1
) {
region.unite(v, u);
}
if (
in.a[v] != 1 &&
in.a[v] == in.a[u]
) {
mono.unite(v, u);
}
}
}
unordered_map<int, vector<int>> verts;
for (int v = 0; v < n; v++) {
if (in.a[v] != 1) {
verts[region.root(v)].push_back(v);
}
}
int ans = 0;
for (auto& [r, vs] : verts) {
set<int> zero_comp;
set<int> two_comp;
for (int v : vs) {
if (in.a[v] == 0) {
zero_comp.insert(mono.root(v));
} else {
two_comp.insert(mono.root(v));
}
}
int c0 = zero_comp.size();
int c2 = two_comp.size();
// 差だけ残る、と仮定
int rem = max(
1,
abs(c0 - c2)
);
ans += (int)vs.size() - rem;
}
// 1-1 は普通に全部取る
for (int v = 0; v < n; v++) {
for (int u : in.g[v]) {
if (
v < u &&
in.a[v] == 1 &&
in.a[u] == 1
) {
ans++;
}
}
}
return min(n - 1, ans);
}
// ============================================================
// 13.
// 「0/2 頂点を全部覆う perfect matching があれば
// 全部縮約可能」
//
// なければ matching 中心で数える。
// ============================================================
int wrong13(const Input& in) {
int non1 = 0;
int c1 = 0;
for (int x : in.a) {
non1 += (x != 1);
c1 += (x == 1);
}
int m = matching02(in);
if (2 * m == non1) {
return in.n - 1;
}
int same = 0;
for (int v = 0; v < in.n; v++) {
for (int u : in.g[v]) {
if (
v < u &&
in.a[v] == in.a[u]
) {
same++;
}
}
}
int ans = same + m;
// matching で作った1と元の1を全部合体できると仮定
if (c1 + m > 0) {
ans += max(
0,
c1 + m - 1
);
}
return min(in.n - 1, ans);
}
// ============================================================
// component tree 上での b-matching
//
// 「同値成分は capacity 個まで
// 0-2 境界辺を使える」
//
// と雑に扱う。
// ============================================================
int bmatching_on_components(
const CompTree& ct,
bool capTwo
) {
int m = ct.m;
// dp[v][parent edge selected?]
vector<array<int, 2>> dp(m);
function<void(int, int)> dfs =
[&](int v, int p) {
int base = 0;
vector<int> gain;
for (int u : ct.g[v]) {
if (u == p) continue;
dfs(u, v);
base += dp[u][0];
if (is02(ct.val[v], ct.val[u])) {
gain.push_back(
1 +
dp[u][1] -
dp[u][0]
);
}
}
sort(
gain.rbegin(),
gain.rend()
);
int cap;
if (ct.val[v] == 1) {
cap = 0;
} else {
cap = ct.sz[v];
}
if (capTwo) {
cap = min(cap, 2);
}
for (int pe = 0; pe <= 1; pe++) {
if (pe > cap) {
dp[v][pe] = NEG;
continue;
}
int take = cap - pe;
int res = base;
for (
int i = 0;
i < (int)gain.size() &&
i < take;
i++
) {
if (gain[i] > 0) {
res += gain[i];
}
}
dp[v][pe] = res;
}
};
dfs(0, -1);
return dp[0][0];
}
// ============================================================
// 14.
// 同値成分を縮約し、
//
// 「成分サイズ = 使えるport数」
//
// とみなす。
//
// 境界辺が同じ元頂点に集中していることを無視。
// ============================================================
int wrong14(const Input& in) {
auto ct = compress_equal(in);
return min(
in.n - 1,
ct.internal +
bmatching_on_components(
ct,
false
)
);
}
// ============================================================
// 15.
// 「長い同値列は2頂点程度残せば十分」
//
// 各同値成分の port capacity を高々2に潰す。
// 3個以上の独立portが必要なケースで死亡。
// ============================================================
int wrong15(const Input& in) {
auto ct = compress_equal(in);
return min(
in.n - 1,
ct.internal +
bmatching_on_components(
ct,
true
)
);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
// ======================================
// ここだけ 1〜15 に変更
// ======================================
constexpr int MODE = 10;
Input in = read_input();
int ans = 0;
if (MODE == 1) ans = wrong1(in);
if (MODE == 2) ans = wrong2(in);
if (MODE == 3) ans = wrong3(in);
if (MODE == 4) ans = wrong4(in);
if (MODE == 5) ans = wrong5(in);
if (MODE == 6) ans = wrong6(in);
if (MODE == 7) ans = wrong7(in);
if (MODE == 8) ans = wrong8(in);
if (MODE == 9) ans = wrong9(in);
if (MODE == 10) ans = wrong10(in);
if (MODE == 11) ans = wrong11(in);
if (MODE == 12) ans = wrong12(in);
if (MODE == 13) ans = wrong13(in);
if (MODE == 14) ans = wrong14(in);
if (MODE == 15) ans = wrong15(in);
cout << ans << '\n';
}