【洛谷5776】[SNOI2013] Quare(状压DP)
- 给定一张\(n\)个点\(m\)条边的图,求边权和最小的边双子图。
- \(n\le12,m\le40\)
边双的拆解
边双相关的一个结论:一个边双可以拆成一个边双子图以及一条链。
因此我们设\(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}\) 的转移显然是非常简单的,就是枚举点集中的一个点 \(y\),将 \(w_{x,y}\) 和 \(u_{x,y}\) 与当前最小值和次小值比较。
\(g_{x,y,i}\)的转移只需考虑枚举\(i\)以外的一个点\(z\)扩展,加上\(w_{y,z}\),将\(z\)加入点集中并更新端点\(y\)为\(z\)。
\(f_i\)的转移就是考虑枚举\(i\)以外的一个点集\(j\),在其中枚举两个端点\(x,y\),列出转移式:
\[f_{i\cup j}=\begin{cases}f_i+g_{x,y,j}+Mn_{x,i}+Mn_{y,i}&x\not=y,\\f_i+g_{x,y,j}+Mn_{x,i}+Sn_{y,i}&x=y\end{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\n",f[l-1]);
}return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒