题解:P5776 [SNOI2013] Quare

· · 题解

:::info[简要题意]{open}

给定一个 n 个点,m 条边的带权无向图,寻找一个边双连通子图,使其边权和最小。可能有重边。

::::

:::info[耳与耳分解的定义]{open}

假如图 G 有一个真子图 G^{\prime},点 aG^{\prime} 上一点。点 aG^{\prime} 外一点相连。假如此点有另一条不经过点 a 的路径连到 G^{\prime},设这条路径连到 G^{\prime} 内的点 b。称点 a 到点 bG^{\prime} 外路径为耳。若一个图 G 可以拆为一个环(即简单回路)与多个耳,则称图 G 可以耳分解。

:::

::::info[结论:图 G 边双连通 \LrarrG 存在耳分解]{open}

:::info[必要性证明:若图 G 存在耳分解,则图 G 边双连通]{open}

由于图 G 存在耳分解,则图 G 必含环。任取一环作为 G_0。当 G_0=G 时,证明完成。否则,设已证得 G 的真子图 G_{i-1} 边双连通,考虑 G_i = G_{i-1} \cup P_i,其中 P_i 是一个耳,P_i 的两端点为 a,b。下证 G_i 边双连通。

由于 G_{i-1} 连通,且点 a,bG_{i-1} 中,故 G_i 连通。

反证:假设 G_i 有割边 e=(u,v)

分类讨论:

删去 e 后,有路径 u\to a\to b\to v,得到一条连接 uv 的路径,故 e 非割边。

显然,因 G_{i-1} 边双连通,因此, G_{i-1} 中无割边。故,e 非割边。

上述两种情况均矛盾,故 G_i 无割边,即 G_i 边双连通。归纳可得,G_k = G 边双连通。

:::

:::info[充分性证明:若图 G 边双连通,则图 G 存在耳分解]{open}

由于图 G 边双连通,图 G 必含环。任取一环作为 G_0。当 G_0=G 时,分解完成。否则,设已构造出 G 的真子图 G_i,且 G_i 满足可由 G_0 逐步添加耳得到。

由于 G_iG 的真子图,故有 G_i\not=G。因此,必存在一条边 (u,v),满足 u \in G_i,v \not \in G_i。根据边双连通的定义,删去边 (u,v) 后必存在另一条路径使得 vG_i 连通。取删去边 (u,v) 后的图 GvG_i 的最短路径为 P,设其终点为 w。令 P'=(u,v)\cup P,则 P'G_i 的一个耳。将 P' 加入 G_iG_{i+1}=G_i\cup P'。重复此过程,每次至少增加一条新边,有限步后得 G,即得耳分解。

:::

::::

$g_{S,i,j}$ 表示当前已考虑的点为 $S$,但是 $S$ 中最后加入的一个耳还未完成的,这个耳以 $i$ 为起点,以 $j$ 为终点,此时已选择的边的最短长度。 $val_{i,j,0/1}$ 表示 $i$ 到 $j$ 的所有边中的最小/次小边权。 1. 开始一个新的耳 从边双连通子图 $S$ 开始,选择 $S$ 中的点 $j$ 和新点 $i$,用边 $(i,j)$ 连接,并计划将耳的另一个端点设为 $S$ 中的点 $k$。这相当于开始构建一个耳,目前只有点 $i$,路径从 $i$ 到 $k$ 的部分尚未完成。注意是开始构建一个耳,这也是和下面扩展一个耳的区别。 ![](https://cdn.luogu.com.cn/upload/image_hosting/xsduj7k6.png) $$g_{S\cup \{i\},i,k}=\min_{ \begin{subarray}{} i\not \in S\\ j\in S\\ k\in S \end{subarray} } f_S+val_{i,j,0}$$ 2. 扩展一个耳 在耳的起点 $i$ 处添加新点 $k$,用边 $(i, k)$ 连接,耳扩展后剩下从 $k$ 到 $j$ 的部分未完成。 ![](https://cdn.luogu.com.cn/upload/image_hosting/1ga1xwcs.png) $$g_{S\cup \{k\},k,j}=\min_{ \begin{subarray}{} i\in S\\ j\in S\\ k\not \in S\\ i\not=j \end{subarray} } g_{S,i,j}+val_{i,k,0}$$ 3. 闭合一个耳 添加新点 $k$,并用两条边分别连接 $k$ 到路径的两个端点 $i$ 和 $j$,形成一个环。此时点集 $S\cup\{k\}$ 成为边双连通子图。 ![](https://cdn.luogu.com.cn/upload/image_hosting/srdpgxk6.png) $$f_{S\cup \{k\}}=\min_{ \begin{subarray}{} i\in S\\ j\in S\\ k\not \in S\\ i\not=j \end{subarray} } \min(f_S,g_{S,i,j})+val_{i,k,0}+val_{j,k,0}$$ 4. 添加单点闭耳(二元环) 从边双连通子图 $S$ 添加新点 $i$,并用两条边(最小和次小)连接 $i$ 到 $S$ 中的点 $j$,形成一个以 $j$ 为端点的二元环。 ![](https://cdn.luogu.com.cn/upload/image_hosting/5glrdld5.png) $$f_{S\cup \{i\}}=\min_{ \begin{subarray}{} i\not \in S\\ j\in S \end{subarray} } f_S+val_{i,j,0}+val_{i,j,1}$$ 总的时间复杂度是 $\Omicron(n^32^n)$ 的。 :::info[代码]{open} ```cpp #include<iostream> #include<cstdio> #include<cstring> #include<cmath> using namespace std; int T,n,m,x,y,z,w_val[15][15][2],f[20005],g[20005][15][15]; int main() { scanf("%d",&T); while(T--) { scanf("%d %d",&n,&m); memset(w_val,0x3f,sizeof(w_val)); memset(f,0x3f,sizeof(f)); memset(g,0x3f,sizeof(g)); for(int i=1;i<=m;i++) { scanf("%d %d %d",&x,&y,&z); if(w_val[x][y][0]>z) { w_val[x][y][1]=w_val[x][y][0]; w_val[x][y][0]=z; } else if(w_val[x][y][1]>z) w_val[x][y][1]=z; w_val[y][x][0]=w_val[x][y][0]; w_val[y][x][1]=w_val[x][y][1]; } for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { if(i>=j) continue; if(w_val[i][j][0]<0x3f3f3f3f&&w_val[i][j][1]<0x3f3f3f3f) f[(1<<(i-1))+(1<<(j-1))]=w_val[i][j][0]+w_val[i][j][1]; g[(1<<(i-1))+(1<<(j-1))][i][j]=w_val[i][j][0]; } } for(int s=1;s<(1<<n);s++) { for(int i=1;i<=n;i++) { if(s&(1<<(i-1))) continue; for(int j=1;j<=n;j++) { if(!(s&(1<<(j-1)))) continue; for(int k=1;k<=n;k++) { if(!(s&(1<<(k-1)))) continue; g[s|(1<<(i-1))][i][k]=min(g[s|(1<<(i-1))][i][k],f[s]+w_val[i][j][0]); } } } for(int i=1;i<=n;i++) { if(!(s&(1<<(i-1)))) continue; for(int j=1;j<=n;j++) { if(!(s&(1<<(j-1)))) continue; if(i==j) continue; for(int k=1;k<=n;k++) { if(s&(1<<(k-1))) continue; g[s|(1<<(k-1))][k][j]=min(g[s|(1<<(k-1))][k][j],g[s][i][j]+w_val[i][k][0]); if(w_val[i][k][0]<0x3f3f3f3f&&w_val[j][k][0]<0x3f3f3f3f) f[s|(1<<(k-1))]=min(f[s|(1<<(k-1))],min(f[s],g[s][i][j])+w_val[i][k][0]+w_val[j][k][0]); } } } for(int i=1;i<=n;i++) { if(s&(1<<(i-1))) continue; for(int j=1;j<=n;j++) { if(!(s&(1<<(j-1)))) continue; if(w_val[i][j][0]>=0x3f3f3f3f||w_val[i][j][1]>=0x3f3f3f3f) continue; f[s|(1<<(i-1))]=min(f[s|(1<<(i-1))],f[s]+w_val[i][j][0]+w_val[i][j][1]); } } } if(f[(1<<n)-1]>=0x3f3f3f3f) printf("impossible\n"); else printf("%d\n",f[(1<<n)-1]); } return 0; } ```