Solution -「CF 1481F」AB Tree

cirnovsky /

link。

理一下逻辑,主要讲一下我做题时的疑惑和其它题解没提到的细节。

首先容易看到,一个必然不劣的贪心策略是把尽量靠近根的层铺成同样的字符。也许会有疑惑,字符串是否本质不同的判定每个位置地位相等。然而在这题里面字符串个数的贡献是和结点所为根的子树大小有关的,所以这个贪心不劣。

设树的最大深度为 dd,那么如果我们能实现每层都铺成同样的字符,答案就是不同长度的字符串个数,也就是 dd

考虑构造出答案为 d+1d+1 的情况,这时候贪心就跑不出来了,具体情况是,会出现一层既有 a 又有 b,其他层都是 pure a 或 pure b

主要问题就在于 miscellaneous level 放在哪里,才能让答案最小。直觉地,我们认为把它放在尽量低(离根更远)的 level 会更优。但是其实贡献结束的地方是叶子,所以其实我们应该把它放在叶子尽量多的 level。这个时候答案为 d+1d+1

根据以上的推理,我们得出一个论断:最优答案在 ddd+1d+1 出现。剩下的问题就是如果判断是前者还是后者。

其实这是一个简单可达性 dp 可以解决的问题,将每一个 level 压成一个 item,其体积为当层的结点个数。但是这个是 O(n2)O(n^2) 的,要考虑优化。有两种解决的道路,分析后可以发现一个根号规模的结论,或者用 std::bitset 优化 dp。

注意 std::bitset 优化 dp 后状态略有不同……

#include<bits/stdc++.h>
#define cmin(x, y) x = std::min(x, y)
#define cmax(x, y) x = std::max(x, y)
#define fors(i, l, r, ...) for(int i = (l), REP##i = (r), ##__VA_ARGS__; i <= REP##i; ++i)
#define dfors(i, r, l, ...) for(int i = (r), REP##i = (l), ##__VA_ARGS__; i >= REP##i; --i)
// observation: amount different answers would be 2: maximum depth, or, it plus 1
int n, _x, dep[100100], nod[100100], leaf[100100], len, vol[100100];
// params: @nod[i]: amout of nodes at level i; @leaf[i]: amout of leaves at lv i;
//     #vol[i]: volume of i-th item
std::bitset<100100> f[2100]; // if can assign it to j perfectly within lefmost i items
std::vector<int> adj[100100], zip[100100];
void pre_dfs(const int x) {
    nod[dep[x]]++,leaf[dep[x]] += adj[x].empty();
    for(const int y : adj[x]) dep[y] = dep[x]+1,pre_dfs(y);
}
int pre_zip() {
    static int vis[100100], tot;
    fors(i, 1, len) {
        if(!vis[nod[i]]) vol[vis[nod[i]] = ++tot] = nod[i];
        zip[vis[nod[i]]].emplace_back(i);
    }
    return tot; // amount of items
}
signed main() {
    std::ios_base::sync_with_stdio(0);
    std::cin.tie(0);
    std::cin >> n >> _x;
    fors(i, 1, n-1, f) std::cin >> f,adj[f].emplace_back(i+1);
    pre_dfs(dep[1] = 1),len = *std::max_element(dep+1, dep+n+1); // processing basic information
    int m = pre_zip(); // compressing levels with same amout of nodes
    fors(i, f[0][0] = 1, m, tmp) {
        f[i] = f[i-1],tmp = zip[i].size();
        for(int j = 1; j <= tmp; j *= 2) tmp -= j,f[i] |= (f[i]<<(vol[i]*j));
        if(tmp > 0) f[i] |= (f[i]<<(vol[i]*tmp));
    }
    std::vector<bool> k(len);
    std::function<void(int, int)> find_path = [&](const int rem, int t) { // reviving way we DP through
        if(rem == 0) return;
        for(const int x : zip[rem]) {
            if(vol[rem] > t || f[rem-1][t]) break;
            t -= vol[rem],k[x-1] = 1;
        }
        find_path(rem-1, t);
    };
    if(f[m][_x]) { // when greedy strategy runs well
        std::cout << len << "\n";
        find_path(m, _x);
        fors(i, 1, n) std::cout << (k[dep[i]-1]?'a':'b');
        return std::cout << "\n", 0;
    }
    std::cout << len+1 << "\n"; // otherwise the answer would be maximum depth plus 1
    int tmp = std::numeric_limits<int>::max(), tmp1 = -1;
    dfors(i, _x, 0) if(f[m][i]) {
        tmp = i; break;
    }
    find_path(m, tmp);
    fors(i, 1, len) if(!k[i-1] && leaf[i] >= _x-tmp) {
        tmp1 = i; break;
    }
    fors(i, 1, n) {
        if(dep[i] == tmp1 && adj[i].empty()) std::cout << (tmp == _x?'b':(++tmp, 'a'));
        else std::cout << (k[dep[i]-1]?'a':'b');
    }
    return std::cout << "\n", 0;
}