ferin blog

Sep 13, 2018 - 3 minute read - 競技プログラミング

CSA #69 D - Cover the Tree

問題ページ

  • 適当な頂点をrootとした根付き木で考える
  • パスとして選ぶ頂点は木の葉を選ぶべきなのが明らか
  • パスの数としてceil(葉の数/2)は少なくとも必要
  • 葉の頂点の集合から2つ組をつくることを繰り返して全ての辺を覆いたい
  • leaf[i] = {rootのi番目の子の部分木に含まれる葉の集合} とする
  • leaf[i],leafj から1つずつ葉を選べばLCAがrootとなってうまくカバーできるpathになる
  • 全ての経路でLCAがrootとなっていれば全ての辺をカバーできている
  • 適当に異なる集合から1つずつ要素を取るを繰り返してみる
  • これを出すと落ちる
    —–解説を見る—–
  • 上で書いたような方法ではleaf[i].size()が偏っている場合がまずい
  • 最終的にleaf[i]にのみ要素が残っていて同じ集合から2つ取るような状況になるとrootがLCAとなるようなパスにならない
  • rootとして適当な頂点を選んでいたところを葉についての重心に変更する
  • こうするとleaf[i].size() < (葉の数/2) となる
  • leaf[i].size() が大きい2つを取って1つずつ葉を取ってくるという操作を繰り返すことで偏ることはない

偏ってるとまずいみたいなケースで重心を選ぶとちょうどいいみたいなことがありそう。

最後のパートの部分問題も割と見ることがありそう。
Q: 数列A (ただしA[i] < sum(A)/2) の状況で2要素を選んでそれぞれ-1する操作を繰り返す。全ての要素を0にするにはどのように操作すればよいか。
A: 数列Aのうち大きい要素を2つ取ってきてそれぞれ-1を繰り返す。pri_queを使えばできる。証明ができない…

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#include <bits/stdc++.h>

using namespace std;
using ll = long long;
#define int ll
using PII = pair<int, int>;
template <typename T> using V = vector<T>;
template <typename T> using VV = vector<V<T>>;
template <typename T> using VVV = vector<VV<T>>;

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define ALL(x) x.begin(), x.end()
#define PB push_back

const ll INF = (1LL<<60);
const int MOD = 1000000007;

template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); }
template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); }
template <typename T> bool IN(T a, T b, T x) { return a<=x&&x<b; }
template<typename T> T ceil(T a, T b) { return a/b + !!(a%b); }
template<class S,class T>
ostream &operator <<(ostream& out,const pair<S,T>& a){
  out<<'('<<a.first<<','<<a.second<<')';
  return out;
}
template<class T>
ostream &operator <<(ostream& out,const vector<T>& a){
  out<<'[';
  REP(i, a.size()) {out<<a[i];if(i!=a.size()-1)out<<',';}
  out<<']';
  return out;
}

int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};

signed main(void)
{
  cin.tie(0);
  ios::sync_with_stdio(false);

  int n;
  cin >> n;
  VV<int> g(n);
  REP(i, n-1) {
    int u, v; cin >> u >> v; u--, v--;
    g[u].PB(v);
    g[v].PB(u);
  }

  if(n == 2) {
    cout << "1" << endl;
    cout << "1 2" << endl;
    return 0;
  }

  // 葉の数と根を決める
  int m = 0, root = -1;
  REP(i, n) {
    if(g[i].size() == 1) m++;
    else root = i;
  }

  // 重心を求める
  auto findCentroid = [&]() {
    V<int> size(n), centroid;
    function<void(int,int)> dfs = [&](int ver, int par) {
      bool is_centroid = true;
      for(auto to: g[ver]) {
        if(to == par) continue;
        dfs(to, ver);
        if(size[to] > m/2) is_centroid = false;
        size[ver] += size[to];
      }
      if(g[ver].size() == 1) size[ver]++;
      if(m - size[ver] > m/2) is_centroid = false;
      if(is_centroid) centroid.PB(ver);
    };
    dfs(root, -1);
    return centroid;
  };
  auto centoroid = findCentroid();

  // 重心からdfsして葉の集合を求める
  int idx = 0;
  VV<int> leaf;
  function<void(int,int)> dfs = [&](int ver, int par) {
    if(g[ver].size()==1) leaf[idx].PB(ver);
    for(auto to: g[ver]) {
      if(to == par) continue;
      dfs(to, ver);
    }
  };
  for(auto i: g[centoroid[0]]) {
    leaf.PB(V<int>());
    dfs(i, centoroid[0]);
    idx++;
  }

  // leaf[i].size() の上位2つを取って経路を一つつくるを繰り返す
  V<PII> ans;
  priority_queue<PII> que;
  REP(i, leaf.size()) {
    if(leaf[i].size()) {
      que.push({leaf[i].size(), i});
    }
  }
  while(que.size()) {
    if(que.size() == 1) {
      PII p1 = que.top(); que.pop();
      ans.PB({leaf[p1.second].back(), centoroid[0]});
      continue;
    }
    PII p1 = que.top(); que.pop();
    PII p2 = que.top(); que.pop();
    ans.PB({leaf[p1.second].back(), leaf[p2.second].back()});
    leaf[p1.second].pop_back();
    leaf[p2.second].pop_back();
    if(leaf[p1.second].size()) que.push({leaf[p1.second].size(), p1.second});
    if(leaf[p2.second].size()) que.push({leaf[p2.second].size(), p2.second});
  }

  cout << ans.size() << endl;
  for(auto i: ans) cout << i.first+1 << " " << i.second+1 << endl;

  return 0;
}