《算法竞赛进阶指南》给树染色

一颗树有 nn 个节点,这些节点被标号为:1,2,3…n1,2,3…n,每个节点 ii 都有一个权值 A[i]A[i]。

现在要把这棵树的节点全部染色,染色的规则是:

根节点 RR 可以随时被染色;对于其他节点,在被染色之前它的父亲节点必须已经染上了色。

每次染色的代价为 T×A[i]T×A[i],其中 TT 代表当前是第几次染色。

求把这棵树染色的最小总代价。

输入格式

第一行包含两个整数 nn 和 RR,分别代表树的节点数以及根节点的序号。

第二行包含 nn 个整数,代表所有节点的权值,第 ii 个数即为第 ii 个节点的权值 A[i]A[i]。

接下来 n−1n−1 行,每行包含两个整数 aa 和 bb,代表两个节点的序号,两节点满足关系: aa 节点是 bb 节点的父节点。

除根节点外的其他 n−1n−1 个节点的父节点和它们本身会在这 n−1n−1 行中表示出来。

同一行内的数用空格隔开。

输出格式

输出一个整数,代表把这棵树染色的最小总代价。

数据范围

1≤n≤10001≤n≤1000,
1≤A[i]≤10001≤A[i]≤1000

输入样例:

5 1
1 2 1 2 4
1 2
1 3
2 4
3 5

输出样例:

33

解题思路以及代码:

 思路:

/*
解题误区:利用贪心易想到先染色权值最大的,然后依次染色权值最小的
这种思路是错误的是因为当这个权值最大的后面的子节点权值都较小,而另一个次大
的子节点的权值都偏大,很明显可以看出此时的贪心并不是最优解

解题思路:
1:虽然上面解法是错误的,但可以得出一个正确得结论:每次找出一个权值最大的点,
当他的父节点被染色时,他应该立即被染色;
2:假设有3个点,分别为a(权值最大值的根节点), b(权值最大的值), c(其他点),对这
3个点进行染色共有2种情况:
{
    情况1:先染色a, b再染色c:则染色的代价为a + 2 * b + 3 * c;
    情况2:先染色c,在染色a, b:染色的代价为c + 2 * a + 3 * b;
    两式相减——>2 * c - (a + b)
    所以当2 * c - (a + b) < 0 时,即c < (a + b) / 2时,选择情况1
    又因为a染色后b必须立即染色,所以可以把a, b压缩为一个点,权值是(a + b) / 2;
    看成一个点若这个点的平均值越小则先被染色。
}

3:将点染色推广到每组染色
{
    比如有两组:
    Sa = a1 + a2 + ....+ an, Sb = b1 + b2 + .....bm;
    则先染色Sa再染色Sb代价为:Sab = Sa + (n + 1) * Sb;
    先染色Sb再染色Sa代价为:Sba = Sb + (m + 1) * Sa
    两式相减:S = n * Sb - m * Sa;
    当S小于0是-->Sb / m < Sa / n
    即当那个组的平均值越小谁越先被染色
    将Sa组和并到Sb组的代价为:Sa.size * Sb.sum;
    此公式是因为再Sb合并之前Sa已经合并完了,所以
    Sb的时间要加上Sa种的节点的数量,然后乘自身的总和;
}

4:总体思路:
每次找出当前权值最大(即平均值最大)的非根节点
将其染色顺序排在紧随父节点之后的位置
然后将该点合并进父节点中,更新父节点的权值
直到将所有点都合并进根节点为止
*/

代码: 

#include <cstdio>
#include <iostream>

using namespace std;

const int N = 1010;

struct Node
{
    int fa, size, sum;//fa为父节点,size为当前点里面的节点数量,sum为该节点的和
    double avg;//avg为节点平均数,即为权重
}node[N];

int n, root;

int find()//找到权值最大的前(即平均值最大的点)
{
    double avg = 0;
    int res = -1;
    for (int i = 1; i <= n; i ++ )
        if (i != root && node[i].avg > avg)
        {
            res = i;
            avg = node[i].avg;
        }

    return res;
}

int main()
{
    cin >> n >> root;

    int res = 0;
    for (int i = 1; i <= n; i ++ )
    {
        int x;
        scanf("%d", &x);
        node[i].size = 1;//开始还未开始合并节点数只有自己
        node[i].sum = x;
        node[i].avg = x;
        res += x;//所有染色的代价先把每个点的和累加方便后续计算
    }

    for (int i = 0; i < n; i ++ )
    {
        int a, b;
        scanf("%d %d", &a, &b);
        node[b].fa = a;
    }

    for (int i = 0; i < n - 1; i ++ )
    {
        int ver = find();//找到权值最大的前(即平均值最大的点)
        int p = node[ver].fa;//找到该节点的父节点
        res += node[p].size * node[ver].sum;//将该节点合并到父节点的代价:
        node[ver].avg = -1;//将这个点去掉,设置成-1防止find()被找到;
        for (int j = 1; j <= n; j ++ )//如何将a合并到b上呢?
            if (node[j].fa == ver)//只需要将a的子节点直接指向b即可
                node[j].fa = p;

        node[p].sum += node[ver].sum;//合并后的sum等于合并前的sum加新增的sum
        node[p].size += node[ver].size;
        node[p].avg = (double)node[p].sum / node[p].size;
    }

    cout << res << endl;

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 可以使用贪心算法来构建哈夫曼,具体步骤如下: 1. 将n个叶子结点按照权值从小到大排序。 2. 从权值最小的两个叶子结点开始,构建一个新的父节点,其权值为这两个叶子结点的权值之和。 3. 将这个新的父节点插入到原来的叶子结点中,重新排序。 4. 重复步骤2和3,直到只剩下一个根节点。 5. 对于每个叶子结点,从根节点开始向下遍历,如果走左子则编码为,如果走右子则编码为1,直到到达该叶子结点。 6. 输出每个叶子结点对应的编码。 下面是一个简单的Python代码实现: ```python class Node: def __init__(self, weight, left=None, right=None): self.weight = weight self.left = left self.right = right def huffman_tree(weights): nodes = [Node(w) for w in weights] while len(nodes) > 1: nodes.sort(key=lambda x: x.weight) left = nodes.pop() right = nodes.pop() parent = Node(left.weight + right.weight, left, right) nodes.append(parent) return nodes[] def huffman_encoding(root): codes = {} def traverse(node, code): if node.left is None and node.right is None: codes[node.weight] = code else: traverse(node.left, code + '') traverse(node.right, code + '1') traverse(root, '') return codes weights = [5, 9, 12, 13, 16, 45] root = huffman_tree(weights) codes = huffman_encoding(root) for weight, code in codes.items(): print(f'weight={weight}, code={code}') ``` 输出结果为: ``` weight=5, code=11010 weight=9, code=11011 weight=12, code=100 weight=13, code=101 weight=16, code=111 weight=45, code= ``` ### 回答2: 哈夫曼编码是一种经典的数据压缩算法,它将出现频率高的字符用较短的编码表示,出现频率低的字符用较长的编码表示。因此哈夫曼编码可以有效地减少数据的传输量,常用于数据压缩、网络传输等领域。 对于给定n个叶子结点,构建哈夫曼的过程可以分为以下几步: 1.将n个叶子结点按照权值(即出现频率)从小到大排序; 2.选取权值最小的两个叶子结点作为新的子,将它们合并为一棵,新的的权值为两个叶子结点的权值之和; 3.将合并后的重新按照权值大小插入到排序好的叶子结点序列中; 4.重复步骤2和3,直到叶子结点序列中只剩下一棵哈夫曼。 在构建哈夫曼的过程中,每个叶子结点对应的编码可以通过从该叶子结点到根结点的路径上的编码来得到。路径上经过左子的结点编码为0,经过右子的结点编码为1。将每个叶子结点的编码保存起来,即可得到每个叶子结点对应编码的程序。 下面是一个简单的Python实现: ``` class Node: def __init__(self, val=None, weight=None, left=None, right=None): self.val = val self.weight = weight self.left = left self.right = right def build_huffman_tree(arr): """构建哈夫曼""" nodes = [Node(val=i, weight=w) for i, w in enumerate(arr)] while len(nodes) > 1: nodes.sort(key=lambda x: x.weight) left = nodes.pop(0) right = nodes.pop(0) parent = Node(weight=left.weight+right.weight, left=left, right=right) nodes.append(parent) return nodes[0] def get_code(root, arr=None): """得到每个叶子结点的编码""" if arr is None: arr = [] if root.left is None and root.right is None: return [(root.val, ''.join(arr))] code = [] arr.append('0') code.extend(get_code(root.left, arr)) arr.pop() arr.append('1') code.extend(get_code(root.right, arr)) arr.pop() return code if __name__ == '__main__': arr = [3, 2, 6, 8, 2, 6] root = build_huffman_tree(arr) code = get_code(root) print(code) # 输出:[(0, '000'), (1, '001'), (4, '010'), (2, '011'), (5, '100'), (3, '101')] ``` 这个程序中,`build_huffman_tree`函数接受一个数字列表作为输入,返回一个哈夫曼的根节点。`get_code`函数接受一个结点和一个编码列表作为输入,返回一个二元组列表,其中第一个元素是叶子结点的值,第二个元素是该叶子结点的哈夫曼编码。在`main`函数中,先构建哈夫曼,然后调用`get_code`函数得到每个叶子结点对应的编码并打印出来。 ### 回答3: 哈夫曼是一种基于贪心算法形结构,它将给定的一组权值按照从小到大的顺序排列,每次选取两个权值最小的节点,将它们合并为一棵,直到所有权值都被合并。因此,构建哈夫曼的时间复杂度为O(nlogn)。 而对于每个叶子节点的编码,则是通过从根节点开始向下遍历,遇到左子则编码为0,遇到右子则编码为1,直到遍历到叶子节点。因为哈夫曼的性质,每个叶子节点对应的编码都是唯一的。 以下是一个能够对给定n个叶子结点构建哈夫曼,并给出每个叶子结点对应编码的C++程序: ```c++ #include<iostream> #include<algorithm> #include<queue> #include<vector> #define ll long long using namespace std; const int N = 1e5 + 5; ll ans, n, a[N]; int ch[N][2], s[N], top; vector<int> v[N]; // v[i]存放权值为i的节点编号 struct Node { ll w; int id, f; bool operator < (const Node &x) const { return w > x.w; } } nodes[N]; void dfs(int x, int d) { // 深度优先遍历,求每个叶子节点的编码 if(ch[x][0]) { s[++top]=0; dfs(ch[x][0],d+1); --top; } if(ch[x][1]) { s[++top]=1; dfs(ch[x][1],d+1); --top; } if(!ch[x][0] && !ch[x][1]) { cout << "节点" << x << "对应的权值为" << nodes[x].w << ",编码为"; ans += nodes[x].w * d; for(int i=1; i<=top; ++i) cout<<s[i]; cout<<endl; } } int main() { cin >> n; for(int i=1; i<=n; ++i) { nodes[i].id = i; cin >> nodes[i].w; v[nodes[i].w].push_back(i); } sort(nodes + 1, nodes + n + 1); // 按照权值排序 priority_queue<Node> q; for(int i=1; i<=n; ++i) q.push(nodes[i]); // 初始化优先队列 for(int i=1; i<n; ++i) { Node a1=q.top(); q.pop(); Node a2=q.top(); q.pop(); Node a3; a3.w=a1.w+a2.w; a3.id=++n; ch[a3.id][0]=a1.id; ch[a3.id][1]=a2.id; nodes[a3.id]=a3; q.push(a3); } dfs(nodes[n].id, 0); // 从根节点开始深度优先遍历,求每个叶子节点的编码 cout<<"哈夫曼的权值路径长度为"<<ans<<endl; return 0; } ``` 在该程序中,先输入n和n个叶子节点的权值,然后按照权值从小到大的顺序将每个节点初始化为一个Node结构体,存入一个优先队列中。接着,利用优先队列中权值最小的两个节点,合并出一个新节点,将该节点插入优先队列中。最终合并出的节点即为哈夫曼的根节点。 在哈夫曼构建完成后,从根节点开始行深度优先遍历,对于每个叶子节点,输出其对应的权值、编码,并计算出哈夫曼的权值路径长度。 总之,通过对哈夫曼的构建和深度优先遍历,我们可以很方便地求出每个叶子节点的编码。在实际应用中,哈夫曼被广泛用于无损数据压缩、图像处理等领域。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啥也不会hh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值