[CF21D] Traveling Graph - 状压dp,最短路,并查集
Description
给定一个有 n (n<=15) 个顶点,m(m<=2000) 条边的带边权无向图,要求从顶点 1 开始经过每一条边至少一次最后回到起点 1,要求走过的边权值总和最小。
Solution
转化为:至少加多少权的边能满足所有点度为偶数
在 u,v 之间加边有直接和间接,为了把间接转化为直接,用 floyd 求 (g[][])
考虑状压 dp,(f[s]) 表示状态 s 集合中的点的度数都为偶数了的最小花费,转移时,每次从没活的点里找两个奇数的转移一下
注意要用并查集判断一下连通性
#include <bits/stdc++.h>
using namespace std;
#define int long long
int getb(int x, int b)
{
--b;
return (x >> b) & 1;
}
void setb(int &x, int b)
{
--b;
x |= (1 << b);
}
const int N = 20;
int g[N][N], f[100005], n, m, d[N], fa[N], siz[N];
int find(int x)
{
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y)
{
x = find(x);
y = find(y);
fa[x] = y;
siz[y] += siz[x];
}
signed main()
{
ios::sync_with_stdio(false);
cin >> n >> m;
memset(g, 0x3f, sizeof g);
for (int i = 1; i <= n; i++)
fa[i] = i, siz[i] = 0;
int ans = 0;
for (int i = 1; i <= m; i++)
{
int u, v, w;
cin >> u >> v >> w;
merge(u, v);
g[u][v] = min(g[u][v], w);
g[v][u] = g[u][v];
siz[u]++;
siz[v]++;
d[u]++;
d[v]++;
ans += w;
}
vector<int> vec;
for (int i = 1; i <= n; i++)
if (find(i) != find(1) && siz[find(i)])
{
cout << -1;
return 0;
}
for (int k = 1; k <= n; k++)
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
}
}
}
int s0 = 0;
for (int i = 1; i <= n; i++)
if (d[i] % 2 == 0)
setb(s0, i);
memset(f, 0x3f, sizeof f);
f[s0] = 0;
for (int s = 0; s < 1 << n; s++)
{
for (int i = 1; i <= n; i++)
if (getb(s, i) == 0)
{
for (int j = 1; j < i; j++)
if (getb(s, j) == 0)
{
int ns = s;
setb(ns, i);
setb(ns, j);
f[ns] = min(f[ns], f[s] + g[i][j]);
}
}
}
int se = (1 << n) - 1;
ans += f[se];
if (ans < 1e12)
cout << ans << endl;
else
cout << -1 << endl;
}