橋梁管理日誌

日誌と言いながら日次での更新はされない模様

Coins on the tree

問題ページ - F - Coins on the tree

概要

N頂点の根付き木が与えられる。頂点iの親はp_iであり、p_i = 0である頂点iが根である。この根付き木上で、M枚のコインを用いて以下のいずれかの操作を合わせてちょうどK回行う。

  • 根にコインが置かれていない場合、新しいコインを根に置く
  • 既にコインが置かれている頂点を1つ選び、コインが置かれていない子のいずれかにコインを移動する

i番目に置いたコインが置かれている頂点の番号をv_iとするとき、列v_1, v_2, ..., v_Mのうちの辞書順最小を求めよ。ただし、M枚のコインを全て置くことができない場合や、ちょうどK回の操作を行うことができない場合は-1を出力せよ。

制約
  • 1 \leq M \leq N \leq 300
  • 0 \leq K \leq 10^5
  • 0 \leq p_i \leq N
  • 与えられるグラフは根付き木である
  • 入力はすべて整数である
解法

辞書順最小を求めたいので、前から貪欲に決めていく。ここで、i枚目のコインを頂点jに置くことが可能であることの条件は、以下の通りである。

  • 頂点jk = 1, 2, ..., i - 1枚目のコインを置いた頂点を根とする部分木に含まれない
  • k = 1, 2, ..., i枚目のコインを置いた頂点を根とする部分木を元の木から取り除いたとき、頂点数がM - i個以上である
  • k = 1, 2, ..., i枚目のコインを置く際に行った操作回数の和をS_iとする。このとき、K - S_ik = i + 1, i + 2, ..., M枚目のコインを置く際に行う操作回数の和としてありえるもののうちの最小以上かつ最大以下である

1つ目の条件は、各頂点にコインが置かれているか否かをbool型の配列で保持しておくことで、DFSを用いて根方向にたどっていくことでO(N)時間で求められる。2つ目の条件と3つ目の条件については、これらを求めるために深さiの頂点がいくつあるかを配列で保持しておく。ただし、ここでは根の深さを1とする。DFSで子孫方向にたどっていき、各深さの頂点数を1ずつ減らしていくことで部分木を取り除く処理と同じことをできる。ただし、重複して取り除くことを避けるため、コインが置かれている頂点に着いた場合は探索を打ち切る。こうすることで、残りの頂点数は深さi = 1, 2, ..., Nの頂点の数の和となるため、2つ目の条件もO(N)時間で求められる。3つ目の条件は、これを満たしていれば操作回数はあとで辻褄を合わせることができる。この条件は、深さが小さい方からM - i個の頂点の深さの和が操作回数の最小となり、深さが大きい方からM - i個の頂点の深さの和が操作回数の最大となることから、これもO(N)時間で求められる。これらの条件を満たさなかった場合は、取り除いた部分木を元に戻す必要があることに注意する。M枚のコインそれぞれについてN個の頂点を前から順に使えるかどうかを調べるため、全体の計算量はO(MN^2)となる。

提出コード
https://atcoder.jp/contests/code-thanks-festival-2018-open/submissions/4362488

#include<bits/stdc++.h>
using namespace std;

const int IINF = 1e9 + 100;

int n, m, k, root;
vector<int> p, dis, dis_cnt, ans;
vector< vector<int> > g;
vector<bool> used;

void calc_depth(int v, int depth){
    dis[v] = depth;
    ++dis_cnt[depth];
    for(auto x : g[v]){
        if(dis[x] > depth + 1){
            calc_depth(x, depth + 1);
        }
    }
    return;
}

bool ancestor(int v){
    if(used[v]){
        return false;
    }
    if(v == root){
        return true;
    }
    return ancestor(p[v]);
}

void descendant(int v, bool mode){
    if(used[v]){
        return;
    }
    if(mode){
        --dis_cnt[dis[v]];
    }else{
        ++dis_cnt[dis[v]];
    }
    for(auto x : g[v]){
        descendant(x, mode);
    }
    return;
}

bool check(int node, int num, int ope){
    if(used[node] || ope + dis[node] > k || !ancestor(node)){
        return false;
    }

    descendant(node, true);
    
    int node_cnt = 0;
    for(int i = 0 ; i <= n ; ++i){
        node_cnt += dis_cnt[i];
    }
    if(node_cnt < m - num - 1){
        descendant(node, false);
        return false;
    }

    int ope_min = 0, ope_max = 0;
    node_cnt = m - num - 1;
    for(int i = 0 ; i <= n ; ++i){
        ope_min += i * min(dis_cnt[i], max(node_cnt, 0));
        node_cnt = max(node_cnt - dis_cnt[i], 0);
    }
    node_cnt = m - num - 1;
    for(int i = n ; i >= 0 ; --i){
        ope_max += i * min(dis_cnt[i], max(node_cnt, 0));
        node_cnt = max(node_cnt - dis_cnt[i], 0);
    }

    if(ope_min <= k - ope - dis[node] && k - ope - dis[node] <= ope_max){
        return true;
    }else{
        descendant(node, false);
        return false;
    }
}

bool solve(int num, int ope){
    if(num == m){
        return ope == k;
    }
    for(int i = 0 ; i < n ; ++i){
        if(check(i, num, ope)){
            ans.push_back(i);
            used[i] = true;
            if(solve(num + 1, ope + dis[i])){
                return true;
            }
            ans.pop_back();
            used[i] = false;
            descendant(i, false);
        }
    }
    return false;
}

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

    cin >> n >> m >> k;

    p.resize(n);
    dis.resize(n, IINF);
    dis_cnt.resize(n + 1);
    g.resize(n);
    used.resize(n);

    for(int i = 0 ; i < n ; ++i){
        cin >> p[i];
        if(p[i] == 0){
            root = i;
            continue;
        }
        --p[i];
        g[p[i]].push_back(i);
    }

    calc_depth(root, 1);

    if(solve(0, 0)){
        for(int i = 0 ; i < m ; ++i){
            cout << ans[i] + 1;
            if(i < m - 1){
                cout << " ";
            }else{
                cout << endl;
            }
        }
    }else{
        cout << -1 << endl;
    }

    return 0;
}