結果
| 問題 | No.3194 Do Optimize Your Solution |
| コンテスト | |
| ユーザー |
👑 tatyam
|
| 提出日時 | 2025-06-26 07:42:31 |
| 言語 | C++23 (gcc 13.3.0 + boost 1.87.0) |
| 結果 |
AC
|
| 実行時間 | 1,108 ms / 3,000 ms |
| コード長 | 8,284 bytes |
| コンパイル時間 | 5,435 ms |
| コンパイル使用メモリ | 340,924 KB |
| 実行使用メモリ | 134,512 KB |
| 最終ジャッジ日時 | 2025-06-27 20:55:48 |
| 合計ジャッジ時間 | 19,015 ms |
|
ジャッジサーバーID (参考情報) |
judge5 / judge1 |
(要ログイン)
| ファイルパターン | 結果 |
|---|---|
| sample | AC * 2 |
| other | AC * 17 |
ソースコード
#ifdef DEBUG
#include <range/v3/all.hpp>
#endif
#include <bits/stdc++.h>
using namespace std;
using u32 = uint32_t;
using u64 = uint64_t;
template<class T> using V = pmr::vector<T>;
/*
木 A の辺ごとに寄与を数える.
A を根付き木とする.
A の頂点 v 以下の部分木に含まれる頂点の index の集合を S とする.B[S] に重み 1 を,それ以外の頂点に重み 0 を割り振ることを考える.
以下のデータ構造があれば良い.
add(v, w): 頂点 v に重み w を追加する.
get(v): sum(dist(v, u) * w[u] for u in V) を求める.
重心分解をすると,log 1 個になる
HL 分解し,heavy-path を下から順に,
*/
u32 get(){
u32 x = 0;
uint8_t c;
while ((c = getchar_unlocked() ^ 48) < 10){
x = x * 10 + c;
}
return x;
}
void solve() {
#ifdef DEBUG
namespace views = ::ranges::views;
namespace ranges = std::ranges;
#endif
// 入力
u64 N = get();
// A の入力
V<pair<u32, u32>> edge(N - 1);
V<u32> deg(N);
for (auto& [u, v] : edge) {
u = get() - 1;
v = get() - 1;
deg[u]++;
deg[v]++;
}
V<V<u32>> A(N);
for (auto&& [a, d] : views::zip(A, deg)) a.reserve(d);
for (auto [u, v] : edge) {
A[u].push_back(v);
A[v].push_back(u);
}
// A を HL 分解
// heavy-edge は G[*][0] に
V<u32> siz(N, 1);
siz.assign(N, 1);
auto hld = [&](auto hld, u32 v) -> void {
if (A[v].empty()) return;
for (u32 i : A[v]) {
erase(A[i], v);
hld(hld, i);
siz[v] += siz[i];
}
auto p = ranges::max_element(A[v], {}, [&](u32 i) { return siz[i]; });
swap(*p, A[v][0]);
};
hld(hld, 0);
// A のオイラーツアー順に頂点番号の relabel
V<u32> label(N, 0);
{
u32 idx = 0;
auto relabel = [&](auto relabel, u32 v) -> void {
label[v] = idx++;
for (u32 i : A[v]) {
relabel(relabel, i);
}
};
relabel(relabel, 0);
}
// A の作り直し
{
V<V<u32>> A_(N);
for (auto&& [i, a] : A | views::enumerate) {
for (u32& x : a) x = label[x];
A_[label[i]] = std::move(a);
}
A = std::move(A_);
}
// heavy_parent: A における親の番号 (light-edge なら -1)
V<u32> heavy_parent(N, -1);
for (auto&& [i, a] : views::enumerate(A)) {
if (a.empty() || i == 0) continue; // 根はやらなくていいので切る
heavy_parent[a[0]] = i;
}
// B を入力
deg.assign(N, 0);
for (auto& [u, v] : edge) {
u = label[get() - 1];
v = label[get() - 1];
deg[u]++;
deg[v]++;
}
V<V<u32>> B(N);
for (auto&& [a, d] : views::zip(B, deg)) a.reserve(d);
for (auto [u, v] : edge) {
B[u].push_back(v);
B[v].push_back(u);
}
// B を重心分解
struct T { u64 cnt = 0, sum = 0; };
V sum_dist(N, array<T, 2>{}); // { (重心が i である連結成分) -> i, (重心が i である連結成分) -> parent[i] }
V<V<pair<array<T, 2>*, u64>>> centroid_ancestor(N); // {連結成分の和,重心までの距離}
for (auto& c : centroid_ancestor) c.reserve(bit_width(N));
{
V<bool> deleted(N, false);
V<pair<u32, u32>> cc; // {index, 部分木サイズ}
auto adj = [&](u32 v) {
return B[v] | views::filter([&](u32 u) { return !deleted[u]; });
};
auto dfs_size = [&](auto dfs_size, u32 parent, u32 v) -> u32 {
// 頂点 v の部分木のサイズを求めながら,頂点のリストを作る
u32 siz = 1;
for (u32 i : adj(v)) if (i != parent) {
siz += dfs_size(dfs_size, v, i);
}
cc.emplace_back(v, siz);
return siz;
};
auto CD = [&](auto CD, u32 v) -> void {
// 頂点 v を含む連結成分を重心分解する
cc.clear();
u32 half_siz = dfs_size(dfs_size, -1, v) / 2;
v = ranges::min(cc, {}, [&](const auto& p) { return p.second >= half_siz ? p.second : u32(-1); }).first;
// 重心を削除
deleted[v] = true;
// centroid_ancestor を更新
auto const p_sum = &sum_dist[v];
auto dfs = [&](auto dfs, u32 parent, u32 v, u32 dist) -> void {
centroid_ancestor[v].emplace_back(p_sum, dist);
for (u32 u : adj(v)) if (u != parent) {
dfs(dfs, v, u, dist + 1);
}
};
dfs(dfs, -1, v, 0);
// 子を重心分解
for (u32 u : adj(v)) CD(CD, u);
};
CD(CD, 0);
}
// B の全頂点に 0 をセット
for (auto& anc : centroid_ancestor) {
u64 dist_prev = 0;
{
auto [p_sum, dist] = anc[0];
auto& [P, Q] = *p_sum;
P.cnt++;
P.sum += dist;
dist_prev = dist;
}
for (auto [p_sum, dist] : anc | views::drop(1)) {
auto& [P, Q] = *p_sum;
Q.cnt++;
Q.sum += dist_prev;
P.cnt++;
P.sum += dist;
dist_prev = dist;
}
}
u64 ans = 0, sum01 = 0;
auto set = [&](u32 v) -> void {
// B で頂点 v の重みを 1 に
u64 dist_prev = 0;
{
auto [p_sum, dist] = centroid_ancestor[v][0];
auto& [P, Q] = *p_sum;
P.cnt--;
P.sum -= dist;
sum01 += P.sum + P.cnt * dist;
P.cnt--;
P.sum -= dist;
dist_prev = dist;
}
for (auto [p_sum, dist] : centroid_ancestor[v] | views::drop(1)) {
auto& [P, Q] = *p_sum;
Q.cnt--;
Q.sum -= dist_prev;
sum01 -= Q.sum + Q.cnt * dist_prev;
Q.cnt--;
Q.sum -= dist_prev;
P.cnt--;
P.sum -= dist;
sum01 += P.sum + P.cnt * dist;
P.cnt--;
P.sum -= dist;
dist_prev = dist;
}
};
auto reset = [&](u32 v) -> void {
// B で頂点 v の重みを 0 に
u64 dist_prev = 0;
{
auto [p_sum, dist] = centroid_ancestor[v][0];
auto& [P, Q] = *p_sum;
P.cnt++;
P.sum += dist;
sum01 -= P.sum + P.cnt * dist;
P.cnt++;
P.sum += dist;
dist_prev = dist;
}
for (auto [p_sum, dist] : centroid_ancestor[v] | views::drop(1)) {
auto& [P, Q] = *p_sum;
Q.cnt++;
Q.sum += dist_prev;
sum01 += Q.sum + Q.cnt * dist_prev;
Q.cnt++;
Q.sum += dist_prev;
P.cnt++;
P.sum += dist;
sum01 -= P.sum + P.cnt * dist;
P.cnt++;
P.sum += dist;
dist_prev = dist;
}
};
auto set_subtree = [&](auto set_subtree, u32 v) -> void {
// 部分木の重みをすべて 1 に
set(v);
for (u32 i : A[v]) {
set_subtree(set_subtree, i);
}
};
auto reset_subtree = [&](auto reset_subtree, u32 v) -> void {
// 部分木の重みをすべて 0 に
reset(v);
for (u32 i : A[v]) {
reset_subtree(reset_subtree, i);
}
};
auto climb = [&](auto climb, u32 v) -> void {
// 部分木の重みをすべて 1 に
set(v);
for (u32 i : A[v] | views::drop(1)) {
set_subtree(set_subtree, i);
}
ans += sum01;
if (u32 p = heavy_parent[v]; p != -1) {
// heavy-edge を登る
climb(climb, p);
} else {
reset_subtree(reset_subtree, v);
}
};
// heavy-path を下から順に処理
for (auto&& [i, a] : A | views::enumerate) {
if (a.size()) continue;
climb(climb, (u32)i);
}
// 出力
cout << ans * 2 << '\n';
}
int main() {
pmr::monotonic_buffer_resource mr{ 256 << 20 };
pmr::set_default_resource(&mr);
cin.tie(0)->sync_with_stdio(0);
solve();
}
tatyam