题意
传送门 AcWing 388 四叶草魔杖
题解
将答案中需要传递能量的节点间连一条边,那么答案为各连通分量上的最小生成树权值之和。 N N N 规模较小, O ( 2 N ) O(2^N) O(2N) 枚举子集,记录满足连通性且节点权值和为 0 0 0 的最小生成树。最后使用状态压缩 D P DP DP 求解答案。设 d p [ i ] dp[i] dp[i] 代表使 i i i 对应的子集各节点能量相同的最小花费,则有 d p [ i ] = d p [ i ⊕ j ] + m s t [ j ] dp[i]=dp[i\oplus j]+mst[j] dp[i]=dp[i⊕j]+mst[j] 其中 j j j 为 i i i 的非空子集, m s t mst mst 代表最小生成树边权和。总时间复杂度 O ( 2 2 N ) O(2^{2N}) O(22N)。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 16, inf = 0x3f3f3f3f;
int N, M, A[maxn], G[maxn][maxn], ds[maxn], mst[1 << maxn], dp[1 << maxn];
bool in[maxn], vs[maxn], fg[1 << maxn];
bool prim(int &res, int cnt)
{
memset(ds, 0x3f, sizeof(ds));
memset(vs, 0, sizeof(vs));
for (int i = 0; i < N; ++i)
if (in[i])
{
ds[i] = 0;
break;
}
for (int k = 0; k < cnt; ++k)
{
int x = -1;
for (int i = 0; i < N; ++i)
if (in[i] && !vs[i] && (x == -1 || ds[i] < ds[x]))
x = i;
if (x == -1)
return 0;
vs[x] = 1, res += ds[x];
for (int i = 0; i < N; ++i)
if (in[i] && !vs[i])
ds[i] = min(ds[i], G[x][i]);
}
return 1;
}
int main()
{
scanf("%d%d", &N, &M);
for (int i = 0; i < N; ++i)
scanf("%d", A + i);
memset(G, 0x3f, sizeof(G));
for (int i = 0; i < N; ++i)
G[i][i] = 0;
for (int i = 0, x, y, z; i < M; ++i)
scanf("%d%d%d", &x, &y, &z), G[x][y] = G[y][x] = z;
for (int i = 1; i < (1 << N); ++i)
{
memset(in, 0, sizeof(in));
int sum = 0, cnt = 0;
for (int j = 0; j < N; ++j)
if (i >> j & 1)
sum += A[j], in[j] = 1, ++cnt;
fg[i] = sum == 0 && prim(mst[i], cnt);
}
memset(dp, 0x3f, sizeof(dp));
dp[0] = 0;
for (int i = 1; i < (1 << N); ++i)
{
if (!fg[i])
continue;
for (int j = i; j; j = (j - 1) & i)
{
if (!fg[j])
continue;
dp[i] = min(dp[i], dp[i ^ j] + mst[j]);
}
}
if (dp[(1 << N) - 1] == inf)
puts("Impossible");
else
printf("%d\n", dp[(1 << N) - 1]);
return 0;
}