Tree Root Transition Algorithms for Maximum Subtree Value

Problem A: Tree Value Maximization

Approach

Define subtree_value[i] as the value generated by the subtree rooted at node i: subtree_value[i] = subtree_size[i] + Σ subtree_value[j] for all children j of i. The initial selection of i as root gives subtree_size[i] value, followed by contributions from its subtrees.

Direct computation for each root yields O(n²) complexity. We optimize using root transition dynamic programming.

Let root_value[i] represent the answer when i is root. Consider transitioning from root x to root y in a tree of size n:

root_value[x] = n + Σ subtree_value[v] for all v in x's subtrees
root_value[x] = n + Σ subtree_value[v] for v in x's children (v ≠ y) + subtree_value[y]
root_value[x] = n + Σ subtree_value[v] for v in x's children (v ≠ y) + subtree_size[y] + Σ subtree_value[u] for u in y's children (u ≠ x)
root_value[x] = n + subtree_value[x] + subtree_size[y] + Σ subtree_value[u] for u in y's children (u ≠ x) - (n - subtree_size[y])
root_value[y] = n + subtree_value[x] + Σ subtree_value[u] for u in y's children (u ≠ x)
∴ root_value[x] = root_value[y] + 2 × subtree_size[y] - n
∴ root_value[y] = root_value[x] - 2 × subtree_size[y] + n

The transition equation for root switching. When n=1, root_value[1] = subtree_value[1].

Implementation

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
using ll = long long;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int node_count;
    cin >> node_count;
    
    vector<vector<int>> graph(node_count + 1);
    for (int i = 1; i < node_count; i++) {
        int u, v;
        cin >> u >> v;
        graph[u].push_back(v);
        graph[v].push_back(u);
    }
    
    vector<ll> subtree_val(node_count + 1), root_val(node_count + 1), sizes(node_count + 1);
    
    auto compute_subtree = [&](auto&& self, int node, int parent) -> void {
        sizes[node] = 1;
        for (int child : graph[node]) {
            if (child == parent) continue;
            self(self, child, node);
            sizes[node] += sizes[child];
            subtree_val[node] += subtree_val[child];
        }
        subtree_val[node] += sizes[node];
    };
    
    compute_subtree(compute_subtree, 1, 0);
    
    ll max_result = 0;
    max_result = root_val[1] = subtree_val[1];
    
    auto transition_roots = [&](auto&& self, int node, int parent) -> void {
        if (node != 1) {
            root_val[node] = root_val[parent] + node_count - 2 * sizes[node];
            max_result = max(max_result, root_val[node]);
        }
        
        for (int child : graph[node]) {
            if (child == parent) continue;
            self(self, child, node);
        }
    };
    
    transition_roots(transition_roots, 1, 0);
    cout << max_result << '\n';
    
    return 0;
}

Problem B: Number Sequence Transformation

Approach

Classic problem modeling numbers as graph nodes. Create directed edges from x to y if y = x×2 or y = x/3 (when divisible by 3). This forms a DAG where we need the longest path of length n, solved via topological sorting.

Implementation

#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
using ll = long long;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n;
    cin >> n;
    
    vector<ll> nums(n + 1);
    for (int i = 1; i <= n; i++)
        cin >> nums[i];
    
    vector<int> indegree(n + 1);
    vector<vector<int>> adj(n + 1);
    
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if (i == j) continue;
            if (nums[i] * 2 == nums[j]) {
                adj[i].push_back(j);
                indegree[j]++;
            }
            if (nums[i] % 3 == 0 && nums[i] / 3 == nums[j]) {
                adj[i].push_back(j);
                indegree[j]++;
            }
        }
    }
    
    queue<int> q;
    for (int i = 1; i <= n; i++) {
        if (indegree[i] == 0) {
            q.push(i);
        }
    }
    
    vector<int> result;
    while (!q.empty()) {
        int current = q.front();
        q.pop();
        result.push_back(current);
        for (int neighbor : adj[current]) {
            if (--indegree[neighbor] == 0) {
                q.push(neighbor);
            }
        }
    }
    
    for (int idx : result)
        cout << nums[idx] << " \n"[idx == result.back()];
    
    return 0;
}

Problem C: Bitwise Value Maximization

Approach

Operation (x & y, x | y) transfers 1-bits between numbers while preserving total count. Since we maximize sum of squares, we should create largest possible numbers. Count 1-bits per position and greedily construct maximum values.

Implementation

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
using ll = long long;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n;
    cin >> n;
    
    vector<ll> arr(n + 1);
    vector<int> bit_count(30);
    
    for (int i = 1; i <= n; i++) {
        cin >> arr[i];
        for (int bit = 0; bit < 20; bit++) {
            if (arr[i] >> bit & 1)
                bit_count[bit]++;
        }
    }
    
    ll total = 0;
    for (int i = 1; i <= n; i++) {
        ll current = 0;
        for (int bit = 0; bit < 20; bit++) {
            if (bit_count[bit] > 0) {
                current |= (1 << bit);
                bit_count[bit]--;
            }
        }
        total += current * current;
    }
    
    cout << total << '\n';
    return 0;
}

Problem D: Position Correction

Approach

When p[i] = i, swap with adjacent element to fix both positions. Scan array and perform swaps when needed.

Implementation

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n;
    cin >> n;
    
    vector<int> perm(n + 1);
    for (int i = 1; i <= n; i++)
        cin >> perm[i];
    
    int swaps = 0;
    for (int i = 1; i <= n; i++) {
        if (perm[i] != i) continue;
        if (i < n) swap(perm[i], perm[i + 1]);
        else swap(perm[i], perm[i - 1]);
        swaps++;
    }
    
    cout << swaps << '\n';
    return 0;
}

Problem E: String Construction Strategy

Approach

Greedy approach: sort strings and place smallest/largest characters alternately. When a's current character > b's current character, switch placement strategy to prevent advantage to opponent.

Implementation

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    
    string str_a, str_b;
    cin >> str_a >> str_b;
    
    sort(str_a.begin(), str_a.end());
    sort(str_b.begin(), str_b.end(), greater<char>());
    
    int total_len = str_a.size();
    
    string front_part = "", back_part = "";
    
    while (str_a.size() > (total_len + 1) / 2) str_a.pop_back();
    while (str_b.size() > total_len / 2) str_b.pop_back();
    
    while (total_len--) {
        if (str_a[0] < str_b[0]) {
            front_part += str_a[0];
            str_a.erase(str_a.begin());
        } else {
            back_part += str_a.back();
            str_a.pop_back();
        }
        
        if (total_len) {
            if (str_a[0] < str_b[0]) {
                front_part += str_b[0];
                str_b.erase(str_b.begin());
            } else {
                back_part += str_b.back();
                str_b.pop_back();
            }
            total_len--;
        }
    }
    
    reverse(back_part.begin(), back_part.end());
    cout << front_part + back_part << '\n';
    
    return 0;
}

Problem F: Tree Range Updates

Approach

Process subtree updates using depth-based difference arrays. During DFS, maintain prefix sums on depth differences. Reverse updates during backtracking to prevent interference between subtrees.

Implementation

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
using ll = long long;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n;
    cin >> n;
    
    vector<vector<int>> tree(n + 1);
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        tree[u].push_back(v);
        tree[v].push_back(u);
    }
    
    int query_count;
    cin >> query_count;
    
    vector<vector<pair<int, int>>> queries(n + 1);
    while (query_count--) {
        int node, depth, value;
        cin >> node >> depth >> value;
        queries[node].emplace_back(depth, value);
    }
    
    vector<ll> depth_diff(n + 1), result(n + 1);
    
    auto dfs_process = [&](auto&& self, int node, int parent, int depth, ll current_sum) -> void {
        for (auto &[d, val] : queries[node]) {
            depth_diff[depth] += val;
            int end_depth = depth + d + 1;
            if (end_depth <= n) {
                depth_diff[end_depth] -= val;
            }
        }
        
        current_sum += depth_diff[depth];
        result[node] = current_sum;
        
        for (int child : tree[node]) {
            if (child == parent) continue;
            self(self, child, node, depth + 1, current_sum);
        }
        
        for (auto &[d, val] : queries[node]) {
            depth_diff[depth] -= val;
            int end_depth = depth + d + 1;
            if (end_depth <= n) {
                depth_diff[end_depth] += val;
            }
        }
    };
    
    dfs_process(dfs_process, 1, 0, 0, 0);
    
    for (int i = 1; i <= n; i++)
        cout << result[i] << " \n"[i == n];
    
    return 0;
}

Problem G: Numerric Sequence Partitioning

Approach

Dynamic programming: dp[i][j] = number of sequences ending with number from j to i. For length L = i-j+1, numbers with length < L are always smaller. Use prefix sums for optimization. For equal lengths, compare using binary search with hashing.

Implementation

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
using ll = long long;
using ull = unsigned long long;

struct StringHash {
    ull base = 13331;
    vector<ull> powers, hashes;
    
    StringHash(string &s) {
        int n = s.size();
        powers.resize(n + 1);
        hashes.resize(n + 1);
        powers[0] = 1;
        hashes[0] = 0;
        for (int i = 1; i < n; i++) {
            powers[i] = powers[i - 1] * base;
            hashes[i] = hashes[i - 1] * base + s[i];
        }
    }
    
    ull get_hash(int l, int r) {
        return hashes[r] - hashes[l - 1] * powers[r - l + 1];
    }
    
    bool equal_segments(int l1, int r1, int l2, int r2) {
        return get_hash(l1, r1) == get_hash(l2, r2);
    }
};

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n;
    cin >> n;
    
    string s;
    cin >> s;
    s = " " + s;
    StringHash hasher(s);
    
    auto compare_segments = [&](int start1, int start2) -> bool {
        int low = 0, high = n - start2, max_match = 0;
        while (low <= high) {
            int mid = (low + high) / 2;
            if (hasher.equal_segments(start1, start1 + mid - 1, start2, start2 + mid - 1)) {
                low = mid + 1;
                max_match = mid;
            } else {
                high = mid - 1;
            }
        }
        
        if (max_match == n - start2) return false;
        return s[start1 + max_match] < s[start2 + max_match];
    };
    
    const ll MOD = 1e9 + 7;
    vector<vector<ll>> dp(n + 1, vector<ll>(n + 1));
    vector<vector<ll>> prefix(n + 1, vector<ll>(n + 1));
    
    for (int i = 1; i <= n; i++)
        dp[i][1] = 1;
    
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= i; j++) {
            if (s[j] == '0') continue;
            int min_idx = max(1, j - (i - j));
            dp[i][j] = (dp[i][j] + (prefix[j - 1][j - 1] - prefix[j - 1][min_idx - 1] + MOD) % MOD) % MOD;
            min_idx--;
            if (min_idx >= 1 && compare_segments(min_idx, j)) {
                dp[i][j] = (dp[i][j] + dp[j - 1][min_idx]) % MOD;
            }
        }
        for (int j = 1; j <= i; j++)
            prefix[i][j] = (prefix[i][j - 1] + dp[i][j]) % MOD;
    }
    
    ll answer = 0;
    for (int i = 1; i <= n; i++)
        answer = (answer + dp[n][i]) % MOD;
    
    cout << answer << '\n';
    return 0;
}

Problem H: Array Equalization

Approach

First verify sum mod n = 0. Transfer all values to first element, then redistribute average. Handle remainders by "loaning" from first element to make values divisible by indices, then returning. Total operations ≤ 3(n-1).

Implementation

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
using ll = long long;

void solve() {
    int n;
    cin >> n;
    
    ll total = 0;
    vector<int> arr(n + 1);
    for (int i = 1; i <= n; i++) {
        cin >> arr[i];
        total += arr[i];
    }
    
    if (total % n != 0) {
        cout << -1 << '\n';
        return;
    }
    
    int target = total / n;
    vector<array<int, 3>> operations;
    
    for (int i = 2; i <= n; i++) {
        if (arr[i] % i != 0) {
            int needed = i - arr[i] % i;
            arr[1] -= needed;
            arr[i] += needed;
            operations.push_back({1, i, needed});
        }
        int transfers = arr[i] / i;
        arr[1] += arr[i];
        arr[i] = 0;
        operations.push_back({i, 1, transfers});
    }
    
    for (int i = 2; i <= n; i++) {
        operations.push_back({1, i, target - arr[i]});
    }
    
    cout << operations.size() << '\n';
    for (auto [from, to, amount] : operations) {
        cout << from << ' ' << to << ' ' << amount << '\n';
    }
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int test_cases;
    cin >> test_cases;
    while (test_cases--) {
        solve();
    }
    
    return 0;
}

Tags: dynamic-programming tree-algorithms graph-theory topological-sort bit-manipulation

Posted on Thu, 14 May 2026 08:30:08 +0000 by zeb