2017多校联合第三场 1005题 hdu 6060 RXD and dividing (超详细!!!)构造

来源:互联网 发布:美工素材网站有哪些 编辑:程序博客网 时间:2024/05/22 07:08

题目链接


题意:

给定一棵 n 个节点的树,1 为根。现要将节点 2 ~ n 划分为 k 块,使得每一块与 根节点 形成的最小斯坦纳树的 边权值 总和最大。


看了题解之后的思考:

题解是:记有向边 (u, v) 长度为 w[v],以点 v 为根的子树的节点总数为 sz[v],那么答案就是 w[v] * min(sz[v], k) 对每个点求个和。

题解的说法是:可以通过构造使得 balabala...

http://bestcoder.hdu.edu.cn/blog/2017-multi-university-training-contest-3-solutions-by-%E6%B4%AA%E5%8D%8E%E6%95%A6/


一开始觉得...这简直是在开玩笑吧。后来仔细画画图,好像真是这么回事。原来之前思考的时候一直陷入了一个误区,至于是什么,我们之后再说。

现在,先来谈一谈,究竟如何构造。


首先,入手点是 每条边 被算了多少次,即每条边的权值对最终答案的贡献。(这种看贡献的想法真的很重要(敲黑板))

其次,从题解中我们可以发现,这个算了多少次 好像就 简简单单地 取了 可能取到的最大值。

然而,这个尽可能的能被允许的最大值 min(sz[v], k) 能保证在每个点都一定能取到吗?

答案是可以的。


首先,要明确的是,对于以 v 为根的子树而言,将其划分的块数越多,对 w[v] 的贡献就越大。

下面开始分类讨论。

1. 

当除根以外所有节点的 sz 值 都 <= k 时,对于任意一个点 v,可将以 v 为根的子树划分为 sz 块,此时贡献最大。

对于任意两个点 v1,v2 而言:

1)若 v2 在 以 v1 为根的子树内(v1 在以 v2 为根的子树内 同理),可将 v1 看成一个整体,其中 sz1 个点划分为 sz1 块即可

2)若 v2 不在以 v1 为根的子树内,且 v1 不在以 v2 为根的子树内,那么 v1 的 sz1 块 与 v2 的 sz2 块就是完全独立的,可以简单地将 sz1 中的任一个集合 与 sz2 中的任一个集合合并,只要保证 sz1 中不同的集合 不与 sz2 中的同一个集合合并, sz2 中不同的集合 也不与 sz1 中的同一个集合合并 即可(其他合并方式也可以,如使 其中一块 自成一个集合等等,只要满足前述条件 并且 合并后的总数 <= k 即可)

反复进行上述操作,最终可得到 sz (sz <= k) 块,并且其中每一步都取了尽可能的最大值

2. 

存在节点 u,其 sz 值 > k,

对于 u 节点与另外一点 v 而言:

1)若 v 在以 u 为根的子树中,显然,u 必有 sz 值 <= k 的孩子节点,若 v 不是,则可递归地再往下找。

不妨就设其为 v,则 v 的 sz 值 <= k,并且已经被划分好为 szv 块。那么,对于 u 的其他没有被划分的孩子节点,只需将 szu - szv 个节点划分为 k - szv 块即可。

2)若 v 不在以 u 为根的子树中,u 也不在以 v 为根的子树中,合并方式同 1. 2) 相同

反复进行上述操作,最终可得到 k 块,并且其中每一步都取了尽可能的最大值


综上,可以构造出这样的集合使得答案取到最大值。



反思:

一开始思维局限在了每一个子树内要划分出一个集合,压根没想到可以把(相对)不相干的点塞到同一个集合中,主要还是受没改题面之前要求 最小值 的影响...

再提一提最小值怎么做吧,学姐给出的做法是,将根节点到其余每个节点的距离从小到大排个序(包括1到1的距离0),要注意的是,节点有几个子树就把这段距离算几遍,用bfs+贪心(优先队列),取前 k 个最小的,然后加上树上本身每条边的权值,即为答案。



后话:

看了题解后写了一个 dfs,结果用测试数据测结果卡了...一狠心在 hdu 上交了一遍竟然 AC 了。跑去看了 std,回来乖乖地又写了一遍 bfs,然后测试数据就也能跑通了...真是......厉害了。第一场的 12 题也是这样,总都是搞得深度很深来卡递归,也是...没办法了Orz

(后面的代码上面 bfs 和 dfs 都有)



AC代码如下:

#include <bits/stdc++.h>#define maxn 1000010int ne[maxn], sz[maxn], w[maxn], n, k, tot, q[maxn], fa[maxn];bool vis[maxn];inline min(int a, int b) { return a < b ? a : b; }typedef long long LL;struct Edge {    int to, dist, ne;    Edge(int a = 0, int b = 0, int c = 0) : to(a), dist(b), ne(c) {}}edge[maxn * 2];void add(int x, int y, int d) {    edge[tot] = Edge(y, d, ne[x]);    ne[x] = tot++;}void dfs(int u, int fa) {    sz[u] = 1;    for (int i = ne[u]; i != -1; i = edge[i].ne) {        Edge e = edge[i]; int v = e.to;        if (v == fa) continue;        w[v] = e.dist;        dfs(v, u);        sz[u] += sz[v];    }}void bfs(int src) {    memset(vis, 0, sizeof(vis));    int f = 0, r = 0;    q[r++] = src; vis[src] = true;    while (r > f) {        int u = q[f]; sz[u] = 1;        ++f;        for (int i = ne[u]; i != -1; i = edge[i].ne) {            Edge e = edge[i]; int v = e.to;            if (vis[v]) continue;            q[r++] = v; vis[v] = true;            fa[v] = u; w[v] = e.dist;        }    }    for (int i = r - 1; i >= 0; --i) sz[fa[q[i]]] += sz[q[i]];}void work() {    memset(ne, -1, sizeof(ne));    tot = 0;    for (int i = 1; i < n; ++i) {        int x, y, d;        scanf("%d%d%d", &x, &y, &d);        add(x, y, d);        add(y, x, d);    }//    dfs(1, -1);    bfs(1);    LL ans = 0;    for (int i = 2; i <= n; ++i) {        ans += (LL)w[i] * min(sz[i], k);    }    printf("%lld\n", ans);}int main() {    while (scanf("%d%d", &n, &k) != EOF) work();    return 0;}