zoukankan      html  css  js  c++  java
  • 【2018.10.2】菌落合并

    【题目描述】
    小 $M$ 培养了 $n$ 个菌落。其中每个菌落有质量和颜色两种属性,颜色只可能为紫色或红色。小 $M$ 想把所有的菌落合并成一个菌落。因为合并的过程非常费劲,小 $M$ 每天只能进行一次合并,整个过程需要进行 $n − 1$ 天。一次合并会将两个菌落变成一个菌落。如果原来两个菌落的颜色相同,两个菌落会进行融合,新的菌落质量为原来两个菌落质量之和,颜色不变;如果原来两个菌落的颜色不同,两个菌落会进行战斗,新的菌落质量为原来两个菌落质量之差,颜色为质量较大的那个菌落。需要特殊说明的是,中间过程中如果产生了质量为 $0$ 的菌落,这个菌落也需要参与后续合并而不能直接舍弃;可以证明将质量为 $0$ 的菌落视为紫色或红色都不影响后续的计算。
    每天的合并结束后,小 $M$ 都需要喂养当前的每个菌落。对于一个质量为 $w$ 的菌落,小 M 需要花费 $w^2$ 单位的能量对它进行一天的喂养。
    小 $M$ 希望你帮他求出菌落的最佳合并顺序,使得喂养所消耗的总能量最少。你只需要输出所需要的最小能量即可。

    【输入格式】
    从文件 germ.in 中读入数据。
    输入的第一行包含一个正整数 $T$,表示数据的组数。对于所有的测试点,保证$ T = 10$。
    对于每组数据,第一行包含一个正整数 $n$,表示最初菌落的个数。
    接下来 $n$ 行,每行包含一个正整数和一个字符串,表示这个菌落的质量和颜色。若字符串为 $743481$,表示这个菌落是紫色;若字符串为 $8B0012$,表示这个菌落是红色。保证不会出现其他的字符串,保证给出的正整数不超过 $10^6$ 。

    【输出格式】
    输出到文件 germ.out 中。
    对于每组数据,输出一行一个整数,表示消耗的最少总能量。注意这个值可能很大,请注意选用合适的数据类型来存储它。

    【样例输入】
    10
    4
    3 743481
    20 743481
    7 8B0012
    18 743481
    3
    10 8B0012
    13 743481
    13 8B0012
    5
    14 743481
    16 8B0012
    2 8B0012
    9 8B0012
    8 8B0012
    4
    11 8B0012
    15 8B0012
    4 8B0012
    8 8B0012
    2
    11 8B0012
    6 8B0012
    4
    8 8B0012
    16 8B0012
    16 8B0012
    3 8B0012
    2
    20 743481
    17 743481
    3
    6 8B0012
    20 743481
    13 8B0012
    4
    19 8B0012
    15 743481
    4 8B0012
    1 743481
    2
    11 8B0012
    14 8B0012

    【样例输出】
    2238
    200
    980
    2688
    289
    3467
    1369
    86
    107
    625

    【样例解释】
    对于样例中的第一组数据,最优的合并方案如下:
    1. 合并紫色的 $20$ 和红色的 $7$,得到紫色的 $13$;喂养的能量花费为 $3^2 +13^2 +18^2 = 502$。
    2. 合并紫色的 $13$ 和紫色的 $3$,得到紫色的 $16$;喂养的能量花费为 $16^2 +18^2 = 580$。
    3. 合并紫色的 $16$ 和紫色的 $18$,得到紫色的 $34¥;喂养的能量花费为 ¥34^2 = 1156$。
    可以证明没有更优的方案。

    【子任务】
    $n<=10$。

    对于全是同一种颜色的情况,不能从小到大两两合并,这样得出来的解并不是最优的,同色的1~10就是个反例。

    可以当做一个模拟退火题:

    模拟退火题的典例——哈密顿回路题有个较优算法:将初始退火序列设为图的最小生成树的遍历。

    也可以当做一个贪心暴力折中题:

    把这题的暴力和贪心对比一下(当然贪心的结果会大一点点),会发现贪心出现的少量错误答案只比暴力的正确答案多一点点,然后输出一下路径,发现贪心的路径与暴力的路径很相似。因此把每一步的$n^2$枚举的所有情况所导致的结果从小到大排序,然后跑前几种即可(Mangod跑了5个就A了)。

    std:

    f(i,s)表示合并i次后时,经过的点的集合是s。

    这个集合s怎么表示?

    我们都知道,s是把集合压成一个数,代表一种情况。

    然而这里并不能只存个表示01二进制序列的数,表示一个数选或不选,因为一个数在被合并入不同的菌落时,是有不同的后效性的(后效性就是说前面的决策对后面所有答案都有影响),因此有多种不同的合并情况需要被统计。

    也就是说,我们需要记录每个数被合并到了哪个菌落里。这就需要并查集的思想了。

    我们让每个初始菌落存一个数,表示这个菌落被合并若干次后,该菌落中编号最小的初始菌落的编号。那么存的这个数相同的初始菌落就都已经被合并到一个菌落里了。

    容易发现,每个初始菌落存的数都不会超过自己的编号。

    那么$n$个初始菌落的编号总种数就是$n!$,一看$n=10$,总种数大约$100w$。这就可以状态压缩了。

    转移方程:

    复杂度:$O(n!

  • 相关阅读:
    Elasticsearch系列---常见搜索方式与聚合分析
    Elasticsearch系列---Elasticsearch的基本概念及工作原理
    Elasticsearch系列---初识Elasticsearch
    记一次ES查询数据突然变为空的问题
    04、管道符、重定向与环境变量
    03、新手必须掌握的Linux命令
    02、安装Linux系统
    01、VM安装教程
    02、HTML 基础
    01、HTML 简介
  • 原文地址:https://www.cnblogs.com/scx2015noip-as-php/p/9737199.html
Copyright © 2011-2022 走看看