P r o b l e m \mathrm{Problem} Problem
给一个n个点m条边的无向连通图(不存在自环或重边),每条边有一个边权,要求割掉若干条边,使1到n只有1条路径(不经过重复点),问割掉的边权和最小是多少。
D a t a \mathrm{Data} Data
S o l u t i o n \mathrm{Solution} Solution
很显然根据数据规模我们可以确定是状态压缩,我们考虑如何转移。
状态很简单,用 f [ s ] [ i ] f[s][i] f[s][i] 表示点即为 s s s ,且 1 1 1 号点最终停留到 j j j 号点,且 i i i 到 j j j 路径只有一条的最大保留数。
状态的转移分成两种:
- 当前转台转移到新的点: f [ i ] [ j ] + v ( j , k ) → f [ i ∪ k ] [ k ] f[i][j]+v(j,k)→f[i∪k][k] f[i][j]+v(j,k)→f[i∪k][k]
- 当两个联通块合并且以当前状态为终点时:
f
[
i
]
[
j
]
+
S
[
k
]
→
f
[
i
∪
k
]
[
j
]
f[i][j]+S[k]→f[i∪k][j]
f[i][j]+S[k]→f[i∪k][j]
S S S表示保留点集为 S S S的所有边的边权和。
接下来考虑如何求解 S S S,显然我们可以采用递推的方法.比如当前状态为 S S S,可以枚举 j j j,用S更新 S ∪ j S∪j S∪j。
C o d e \mathrm{Code} Code
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 20;
int read(void)
{
int s = 0, w = 0; char c = getchar();
while (c < '0' or c > '9') w |= c == '-', c = getchar();
while (c >= '0' and c <= '9') s = s*10+c-48, c = getchar();
return w ? -s : s;
}
int n, m, sum = 0;
int f[1<<N][N], S[1<<N], val[N][N], in[1<<N];
vector <int> a[N];
int main(void)
{
n = read(), m = read();
memset(f,-1,sizeof f);
for (int i=1,x,y,v;i<=m;++i)
{
x = read()-1, y = read()-1, v = read();
val[x][y] = val[y][x] = v;
sum += v;
a[x].push_back(y);
a[y].push_back(x);
}
for (int i=0;i<1<<n;++i)
for (int j=0;j<n;++j)
if (((i>>j) & 1) == 0 and in[i|(1<<j)] == 0) {
int cnt = S[i];
for (int k=0;k<a[j].size();++k)
if ((i >> a[j][k]) & 1) cnt += val[j][a[j][k]];
S[i|(1<<j)] = cnt;
}
f[1][0] = 0;
for (int i=0;i<1<<n;++i)
for (int j=0;j<n;++j) if (((i >> j) & 1) and f[i][j] != -1)
{
if (f[i][j] == -1) continue;
for (int k=0;k<n;++k) if (((i >> k) & 1) == 0 && val[j][k])
f[i|(1<<k)][k] = max(f[i|(1<<k)][k],f[i][j]+val[j][k]);
int t = (1<<j) | (((1<<n)-1)^i);
for (int k=t;k;k=(k-1)&t)
if ((k >> j) & 1) f[i|k][j] = max(f[i|k][j],f[i][j]+S[k]);
}
cout<<(sum - f[(1<<n)-1][n-1]);
return 0;
}