一个边双连通图一定可以由一个点不断套上环形成。那么考虑DP,可以每次选一条链,然后两端和原集合连起来更新。
设
fS
f
S
表示集合
S
S
的答案。
因为要选一条链,所以还要维护 表示把
S
S
集合中的点连成一条链,两端为 和
y
y
的最小代价, 可以DP求出。
由于要和原集合连起来,再求出
g[S][x]
g
[
S
]
[
x
]
表示
x
x
和 集合间的边的最小值。因为一条链也可能是一个点,所以还要求次小值。
转移方程比较简单。
时间复杂度
O(3nn2)
O
(
3
n
n
2
)
。
#include<bits/stdc++.h>
using namespace std;
const int N=12;
const int M=200;
const int INF=1e9;
int k,n,m,T;
int he[N],nx[M],t[M],w[M],num;
int f[1<<N],g[1<<N][N][2],h[1<<N][N][N];
void Add(int x,int y,int z) {
t[++num]=y;nx[num]=he[x];he[x]=num;w[num]=z;
}
int Mn(int& x,int y) {
x=min(x,y);
}
int main() {
scanf("%d",&T);
while(T--) {
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++) he[i]=0;num=0;
while(m--) {
int x,y,z;
scanf("%d%d%d",&x,&y,&z);--x;--y;
Add(x,y,z);Add(y,x,z);
}
for(int i=1;i<(1<<n);i++) {
f[i]=INF;
for(int j=0;j<n;j++)
for(int k=0;k<n;k++)
h[i][j][k]=INF;
}
for(int i=0;i<(1<<n);i++)
for(int j=0;j<n;j++)
if(!(i>>j&1)){
int mn=INF,mn2=INF;
for(int k=he[j];k;k=nx[k])
if(i>>t[k]&1) {
if(w[k]<mn) mn2=mn,mn=w[k];else if(w[k]<mn2) mn2=w[k];
}
g[i][j][0]=mn;g[i][j][1]=mn2;
}
for(int i=0;i<n;i++) h[1<<i][i][i]=0;
for(int i=0;i<(1<<n)-1;i++)
for(int j=0;j<n;j++)
if(i>>j&1)
for(int k=0;k<n;k++)
if(i>>k&1)
if(h[i][j][k]<INF){
for(int p=he[j];p;p=nx[p])
if(!(i>>t[p]&1)) Mn(h[i|(1<<t[p])][t[p]][k],h[i][j][k]+w[p]);
for(int p=he[k];p;p=nx[p])
if(!(i>>t[p]&1)) Mn(h[i|(1<<t[p])][j][t[p]],h[i][j][k]+w[p]);
}
for(int i=0;i<n;i++) f[1<<i]=0;
for(int i=1;i<(1<<n);i++)
if((-i&i)<i){
int mn=INF;
for(int j=0;j<n;j++)
if(i>>j&1){
int t=i^(1<<j);
if(f[t]==INF) continue;
if(i>>j&1) Mn(mn,f[t]+g[t][j][0]+g[t][j][1]);
}
for(int j=(i-1)&i;j;j=(j-1)&i) {
int t=i^j;
if((-t&t)==t||f[j]==INF) continue;
for(int x=0;x<n;x++)
if((t>>x&1)&&g[j][x][0]<INF)
for(int y=0;y<n;y++)
if(x!=y&&(t>>y&1)&&g[j][y][0]<INF&&h[t][x][y]<INF)
Mn(mn,f[j]+h[t][x][y]+g[j][x][0]+g[j][y][0]);
}
f[i]=mn;
}
if(f[(1<<n)-1]==INF) puts("impossible");else printf("%d\n",f[(1<<n)-1]);
}
return 0;
}