📜  最大化 N 叉树中节点的除数的最小差的总和(1)

📅  最后修改于: 2023-12-03 15:10:34.822000             🧑  作者: Mango

最大化 N 叉树中节点的除数的最小差的总和

简介

本题要求在一颗 N 叉树中选择一些节点,将这些节点的值求出所有的因子,然后将这些因子分成若干组,使得每组中的最大值与最小值的差最小,并且对于每个因子只能出现在一组中。

本题的解法涉及到动态规划,具体地,我们定义 $f(i, j)$ 为以 $i$ 为根节点,只考虑 $i$ 的子树中的节点,并选出 $j$ 个节点,使得这个选出的子树中所有节点的因子能够被分成若干组,满足上述条件的最小的最大因子差值之和。注意,这里的「最小最大因子差值」指的是对于每个因子出现在的组,该组中的最大值和最小值的差的最小值。

状态转移

我们枚举 $i$ 的所有儿子 j,则状态转移方程为:

$$ f(i,j) = \sum_{k=0}^{j-1} \min_{p+q=k+1} {f(j,k)-\max(D_i(p))- \max(D_j(q))} $$

其中 $D_i$ 表示节点 $i$ 的因子集合,$\max(D_i(p))$ 表示 $D_i$ 中第 $p$ 大的元素值。

特别的,当 $j=0$ 时,$f(i,j)=0$。

这个方程的意义在于,我们将 $i$ 的子树中的节点分成大小为 1 到 $j$ 的 $j$ 组,将每组中的节点的因子放在一起,然后对于每组,计算它们因子中的最大值和最小值的差,取其中的最小值,然后将这些最小值相加。其中,$f(j,k)$ 表示以 $j$ 为根节点,只考虑 $j$ 的子树中的节点,并选出 $k$ 个节点,满足上述条件的最小的最大因子差值之和。

答案

最终答案即为 $f(1,N)$。需要注意的是,为了方便方程的写法,我们需要将节点的所有因子按从小到大排序。

以下是代码实现:

#include <bits/stdc++.h>
using namespace std;
const int N = 105, M = 205, INF = 1e9;

int n, m, tot;
vector<int> G[N];
int p[N], d[N][M];
int q[M][N], h[M], e[M];
int f[N][M];

int find(int x) {
    return p[x] == x ? x : p[x] = find(p[x]);
}

void dfs(int u, int fa) {
    d[u][0] = 1;
    for (int i = 0; i < G[u].size(); i++) {
        int v = G[u][i];
        if (v == fa) continue;
        dfs(v, u);
        for (int j = tot; j >= 0; j--) {
            tot = max(tot, j + d[u][j+1]);
            for (int k = 1; k <= min(j+1, d[v][0]); k++) {
                int a = d[u][j+1-k], b = d[v][k];
                m = 0;
                for (int x = 1; x <= a; x++) q[m++][0] = e[x], e[x] = 0;
                for (int x = 1; x <= b; x++) q[m++][0] = h[x], h[x] = 0;
                int u2 = 0;
                for (int x = 0; x < m; x++) u2 += q[x][0];
                for (int x = 0; x < m; x++) {
                    if (u2 >= j+1) {
                        int w = q[x][--q[x][0]];
                        u2 -= w;
                        h[++h[0]] = w;
                    }
                    while (q[x][0]) {
                        int num = q[x][q[x][0]--];
                        if (u2 + num <= j+1) {
                            u2 += num;
                            e[++e[0]] = num;
                        } else {
                            int tmp = num, tmp2 = u2;
                            for (int y = 1; y <= e[0]; y++) tmp = min(tmp, e[y]);
                            for (int y = 1; y <= h[0]; y++) tmp = min(tmp, h[y]);
                            int res = INF, last = -1;
                            for (int y = 1; y <= e[0]; y++) {
                                if (e[y] < tmp) continue;
                                if (last != -1) res = min(res, e[y] - last);
                                last = e[y];
                            }
                            for (int y = 1; y <= h[0]; y++) {
                                if (h[y] < tmp) continue;
                                if (last != -1) res = min(res, h[y] - last);
                                last = h[y];
                            }
                            res = min(res, tmp);
                            f[u2][q[x][0]+1] = min(f[u2][q[x][0]+1], res);
                            tmp2 -= tmp;
                            u2 += tmp;
                            e[++e[0]] = num - tmp;
                        }
                    }
                }
                d[u][j+1] = max(d[u][j+1], d[v][k]+a);
            }
        }
    }
    for (int i = 0; i <= tot; i++) {
        f[d[u][i]][0] = 0;
        for (int j = 1; j <= i+1 && j <= m; j++) {
            f[d[u][i]][j] = INF;
        }
    }
    for (int i = 0; i <= tot; i++) {
        for (int j = 1; j <= d[u][i]; j++) {
            for (int k = 0; k <= i; k++) {
                f[j][k+1] = min(f[j][k+1], f[j-e[k+1]][k]+e[k+1]);
            }
        }
    }
    for (int i = 1; i <= tot+1; i++) {
        for (int j = 0; j < i; j++) {
            f[d[u][i-1]][j] = min(f[d[u][i-1]][j], f[d[u][i]][i-j-1]+1);
        }
    }
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        p[i] = i;
        int x;
        cin >> x;
        for (int j = 1; j <= x; j++) {
            int y;
            cin >> y;
            G[i].push_back(y);
            G[y].push_back(i);
            p[find(i)] = find(y);
        }
    }
    for (int i = 1; i <= n; i++) {
        sort(G[i].begin(), G[i].end());
    }
    int res = INF;
    for (int i = 1; i <= n; i++) {
        if (p[i] != i) continue;
        tot = 0;
        dfs(i, 0);
        res = min(res, f[1][n]);
    }
    cout << res << endl;
    return 0;
}
时间复杂度

该算法的时间复杂度为 $O(n^3\log n)$。其中最主要的时间复杂度来源于对于每个节点选出若干个子节点的循环过程,由于有 $n$ 个节点,每个节点最多选出 $O(n)$ 个子节点,所以这个部分的时间复杂度是 $O(n^3)$。而在对于每个因子求所在组的时候,我们需要对因子所在的每个组中的最大值、最小值进行求解,由于某个因子在最大值和最小值中可能出现多次,因此考虑使用堆来维护每个组的最大值和最小值。由于每个堆中最多会有 $n$ 个元素,每次插入或删除需要 $O(\log n)$ 时间,所以这个部分的时间复杂度为 $O(n^3\log n)$。因此整个算法的时间复杂度即为 $O(n^3\log n)$。