- 给定一张(n)个点(m)条边的图,求边权和最小的边双子图。
- (nle12,mle40)
边双的拆解
边双相关的一个结论:一个边双可以拆成一个边双子图以及一条链。
因此我们设(f_i)表示使得点集(i)成为边双的最小边权和,(g_{x,y,i})表示以(x,y)为两端、由点集(i)中的点构成的链的最小边权和,并设(Mn_{x,i}/Sn_{x,i})表示(x)到点集(i)的最小/次小边权辅助转移。
记录两点(x,y)间的最小边权为(w_{x,y}),次小边权为(u_{x,y})。
(Mn_{x,i}/Sn_{x,i})的转移显然是非常简单的,就是枚举点集中的一个点,将(w_{x,i})和(u_{w,i})与当前最小值和次小值比较。
(g_{x,y,i})的转移只需考虑枚举(i)以外的一个点(z)扩展,加上(w_{y,z}),将(z)加入点集中并更新端点(y)为(z)。
(f_i)的转移就是考虑枚举(i)以外的一个点集(j),在其中枚举两个端点(x,y),列出转移式:
[f_{icup j}=egin{cases}f_i+g_{x,y,j}+Mn_{x,i}+Mn_{y,i}&x
ot=y,\f_i+g_{x,y,j}+Mn_{x,i}+Sn_{y,i}&x=yend{cases}
]
代码:(O(3^nn^2))
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 12
#define INF (int)7e6
#define Gmin(x,y) (x>(y)&&(x=(y)))
using namespace std;
int n,m,w[N+5][N+5],u[N+5][N+5],f[1<<N],g[N+1][N+1][1<<N],Mn[N+1][1<<N],Sn[N+1][1<<N];
int main()
{
RI Tt,i,j,k,l,x,y,z;scanf("%d",&Tt);W(Tt--)
{
for(scanf("%d%d",&n,&m),i=1;i<=n;++i) for(j=1;j<=n;++j) w[i][j]=u[i][j]=i^j?INF:0;
for(i=1;i<=m;++i) scanf("%d%d%d",&x,&y,&z),
w[x][y]>z?(u[x][y]=u[y][x]=w[x][y],w[x][y]=w[y][x]=z):u[x][y]=u[y][x]=min(u[x][y],z);//求出两点间最小边权和次小边权
for(l=1<<n,i=1;i^l;++i) f[i]=INF;for(i=1;i<=n;++i) for(j=1;j<=n;++j) for(k=1;k^l;++k) g[i][j][k]=INF;//初始全赋成INF
for(i=1;i<=n;++i) f[1<<i-1]=g[i][i][1<<i-1]=0;//单点赋成0
for(i=1;i<=n;++i) for(j=1;j^l;++j) for(Mn[i][j]=Sn[i][j]=INF,x=1;x<=n;++x)//预处理点到点集的最小边权/次小边权
(j>>x-1&1)&&(Mn[i][j]>w[i][x]?(Sn[i][j]=min(Mn[i][j],u[i][x]),Mn[i][j]=w[i][x]):Gmin(Mn[i][j],w[i][x]));//枚举点集中每个点比较
for(k=1;k^l;++k) for(i=1;i<=n;++i) for(j=1;j<=n;++j)//预处理g
for(x=1;x<=n;++x) !(k>>x-1&1)&&Gmin(g[i][x][k|(1<<x-1)],g[i][j][k]+w[j][x]);//枚举一个新端点扩展
for(i=1;i^l;++i) for(j=1;j^l;++j) if(!(i&j)) for(x=1;x<=n;++x) for(y=1;y<=n;++y)//DP求解f
(j>>x-1&1)&&(j>>y-1&1)&&Gmin(f[i|j],f[i]+g[x][y][j]+Mn[x][i]+(x^y?Mn:Sn)[y][i]);//根据x,y是否相同简单讨论
f[l-1]==INF?puts("impossible"):printf("%d
",f[l-1]);
}return 0;
}