首页 > 其他分享 >题解:P10949 四叶草魔杖

题解:P10949 四叶草魔杖

时间:2024-10-22 20:49:10浏览次数:8  
标签:dep P10949 fa 魔杖 题解 int ans 能量 calc

2024/10/16 更新:

  • 修改了状态的枚举方式,时间复杂度变为 \(O(3^n)\)。

题目传送门

前言

本篇题解默认您已熟练掌握最小生成树、状压 dp 及其应用,如果您还不会,请先阅读相关博客。

分析

我们要选出一条边,通过边转移能量,使得所有宝石的能量都为 \(0\)。

这看上去挺麻烦的,让我们挖掘一下题目的性质。可以发现:

  • 传递时能量总和不会变。

  • 每条边的花费和传递的能量的多少没有关系。

请读者注意这两条性质,可以发现,如果一个联通块的能量总和为 \(0\),则可以通过一系列的转移使得这个连通块的能量均为 \(0\)。

而要使一堆点联通,至少需要点数 \(-1\) 条边,这就形成了一棵树,而最优的答案就是最小生成树的权值和,我们可以用 Kruskal 算法解决。

那这道题是求整个图的最小生成树吗?显然不是,因为图不连通也可能更优。

这里给出一个例子,其中点 \(1,2,3,4,5,6\) 的权值分别为 \(-5,-5,10,-5,-5,10\),边权已在图中标出。

如果我们求整个图的最小生成树,求出来的答案会是 \(5\),但是可以发现,我们选择 \(1-2,1-3,4-5,4-6\) 这四条边也同样可以满足条件,但花费减少到了 \(4\)。

这时候,数据范围就有用了,题目中 \(N\) 的范围为 \(1 \leq N \leq 16\)。我们就可以使用状压 dp 了。

设 \(s_i\) 表示状态为 \(i\) 时的能量总和,\(calc_i\) 表示状态为 \(i\) 时的最小生成树的花费,\(ans_i\) 表示状态为 \(i\) 时的答案。

显然,我们只需要在 \(s_i = 0\) 时才计算 \(calc_i\),因为在其他情况下计算 \(calc_i\) 并不会被 \(ans_i\) 用到。\(calc_i\) 能使用 Kruskal 算法计算。并且 \(calc_0=0\)。

接下来考虑计算答案,答案肯定由几个能量总和为零的连通块的答案相加而来,于是可以列出如下状态转移方程:

\[ans_i = \begin{cases} inf & s_i \ne 0 \\ \min_{j}^{s_j=0 \operatorname{and} j \in i}\{ans_{i \operatorname{xor} j} + calc_j\} & s_i = 0 \end{cases}\]

其中 \(i,j\) 是枚举的状态。

初始 \(ans_0=0\)。

最终答案为 \(ans_{2^n-1}\)。

时间复杂度为 \(O(3^n)\)。

代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define N 16
int n, m, a[N], fa[N], dep[N], s[1 << N], calc[1 << N], ans[1 << N];
struct stu
{
    int u, v, w;
    friend bool operator<(stu a, stu b)
    {
        return a.w < b.w;
    }
} to[N * N];
int find(int x)
{
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y)
{
    x = find(x);
    y = find(y);
    if (x == y)
    {
        return;
    }
    if (dep[x] > dep[y])
    {
        fa[y] = x;
        dep[x] += dep[y];
    }
    else
    {
        fa[x] = y;
        dep[y] += dep[x];
    }
}
int init(int x)
{
    for (int i = 0; i < n; i++)
    {
        fa[i] = i;
        dep[i] = 1;
    }
    int sum = __builtin_popcount(x) - 1, as = 0;
    if (sum <= 0)
    {
        return 0;
    }
    for (int i = 0; i < m; i++)
    {
        int u = to[i].u, v = to[i].v, w = to[i].w;
        if ((!(x & (1 << u))) || (!(x & (1 << v))))
        {
            continue;
        }
        if (find(u) == find(v))
        {
            continue;
        }
        sum--;
        as += w;
        merge(u, v);
        if (!sum)
        {
            break;
        }
    }
    if (sum != 0)
    {
        return 0x1f1f1f1f1f1f1f1f;
    }
    else
    {
        return as;
    }
}
signed main()
{
    cin >> n >> m;
    for (int i = 0; i < n; i++)
    {
        cin >> a[i];
    }
    for (int i = 1; i < (1 << n); i++)
    {
        for (int j = 0; j < n; j++)
        {
            if (i & (1 << j))
            {
                s[i] += a[j];
            }
        }
    }
    for (int i = 0; i < m; i++)
    {
        cin >> to[i].u >> to[i].v >> to[i].w;
    }
    sort(to, to + m);
    memset(calc, 0x1f, sizeof(calc));
    calc[0] = 0;
    for (int i = 1; i < (1 << n); i++)
    {
        if (s[i] == 0)
        {
            calc[i] = init(i);
        }
    }
    memset(ans, 0x1f, sizeof(ans));
    ans[0] = 0;
    for (int i = 0; i < (1 << n); i++)
    {
        if (s[i] == 0)
        {
            for (int j = i; j; j = (j - 1) & i)
            {
                ans[i] = min(ans[i], ans[i ^ j] + calc[j]);
            }
        }
    }
    int ANS = ans[(1 << n) - 1];
    if (ANS >= 1e15)
    {
        cout << "Impossible";
        return 0;
    }
    cout << ANS << endl;
    return 0;
}

通过仅用时 47ms。

标签:dep,P10949,fa,魔杖,题解,int,ans,能量,calc
From: https://www.cnblogs.com/awmmmmmm/p/18493708

相关文章

  • 题解:AT_joisc2019_k 合併 (Mergers)
    题目传送门前言联考题,被初一的我切了。看到题解区里没有Tarjan做法,于是来补一篇Tarjan题解。分析因为相同州的城市不会分裂,所以可以给相同州的成市连边(注意不是两两连边,连成一个环就行),发现把国家分成两个部分就相当于断掉一条道路。那么如果整个国家就是一个边双连通分量,......
  • 题解:P11207 「Cfz Round 9」Rose
    可以考虑把字符串\(s\),\(t\)按\(s_1t_1s_2t_2\dotss_nt_n\)拼接,记为\(a\)。为了方便表述,这里分别把PVW表示为012。Subtask0我会暴力!可以直接在\(a\)上进行dfs,复杂度为\(O(3^{2n})\)。Subtask1我会找性质!注意到答案只有可能是\(0,1,2\),因为在最坏情况下,只......
  • 题解:P11204 「Cfz Round 9」Lone
    首先可以观察出把木棍平均分是最优的。然后平均分后最多只有两种长度的木棒,长度分别为\(\lfloor\frac{m}{n}\rfloor\)和\(\lfloor\frac{m}{n}\rfloor+1\)。最后check一下就行了。代码:#include<bits/stdc++.h>usingnamespacestd;#defineintlonglong#define......
  • P2934 [USACO09JAN] Safe Travel G 题解
    一个用平衡树,不用脑子的写法。(目前没有用平衡树的诶。)题意不经过最短路的最后一条边的最短路,保证最短路唯一。思路看到最短路唯一容易想到建出的最短路DAG其实是最短路树(以\(1\)为根)。那题意转化为求每个节点不经过与父亲的连边,所能到根节点的最短路。容易发现每个点的......
  • 【NOIP2021】方差 题解
    前言题目链接:洛谷;LOJ;UOJ。题意简述给你单调不降序列\(\{a_n\}\),你可以让\(a_i\getsa_{i-1}+a_{i+1}-a_i\),求操作后方差的最小值。\(n\leq10^4\),\(1\leqa_i\leq600\)。题目分析仔细观察操作,发现实际上是将\(a_i\)按照\(a_{i-1}\)和\(a_{i+1}\)的......
  • 题解:P10977 Cut the Sequence
    题目传送门分析看到这种题就可以想到动态规划,先设状态:$f_i$表示考虑前$i$个数,所需要的最小代价。发现$f_i$可以从所有$i$以前的状态加后一段区间转移过来,于是可以列出状态转移方程:$$f_i=\min_{j=i-1}^{s_i-s_j\leqm}(f_j+\max_{k=j+1}^i)$$其中$j$......
  • [题解]P2671 [NOIP2015 普及组] 求和
    P2671[NOIP2015普及组]求和可以发现我们对相同颜色且编号奇偶性相同的元素归为一组,组内的元素两两都满足题目条件,且这样可以不重不漏覆盖所有答案。设分完组之后,某一组内的元素编号分别是\(a_1,a_2,\dots,a_q\),数字分别是\(b_1,b_2,\dots,b_q\),则根据题意,该组的答案是:\[\lar......
  • 20241021 校测T1 致敬传奇捆绑测试题目(Perm) 题解
    题解:致敬传奇捆绑测试题目Perm来自不知道什么时候的回忆。给定正整数\(n\),一个\(1\simn\)的排列\(p\)是一个好排列,当且仅当使得对于任意\(1\lek<n\),都有\(\sum_{i=1}^kp_i>p_{k+1}\)。现在请你求出字典序第小的好排列\(p\)。\(1\len\le10^6\),\(1\lek\le......
  • [ARC133E] Cyclic Medians 题解
    一点不会套路。思路对于中位数相关,发现我们不好直接表示与中位数有关的内容。不妨枚举\(x\),把大于\(x\)的标为\(1\),小于等于\(x\)的标为\(0\),这样把所有最终为一的方案数加起来就是原来的答案。大概是这样一个东西:\[k=\sum_{i=0}^k[i<k]\]这个怎么求呢。发现若一组......
  • Public NOIP Round #7 T3 黑白棋子 题解
    Description有一棵\(n\)个点的树,顶点的编号为\(1\)到\(n\)。对于树中的每个顶点,可能存在一个白色的棋子、一个黑色的棋子,或者没有棋子。树上正好有\(w\)个白色棋子和\(b\)个黑色棋子。另外,对于每一对具有相同颜色棋子的顶点,存在一条路径,路径上的每个顶点都包含相同颜色......