[SNOI 2013] Quare
作为一道求包含所有点的最小权边双连通子图的状压 DP 题,做法与其强连通分量版本 https://codeforces.ml/gym/102759/problem/C 是近乎一样的。
首先我们需要了解 耳分解。
这里这样定义耳(不知道是否标准):对于(强)连通图
定义一张图是可耳分解的,当且仅当可以每次从中删去一个耳,并且最终所有边都被删除。
我们有如下定理:
一张有向图是可耳分解的,当且仅当它强连通。
一张无向图是可耳分解的,当且仅当它边双联通。
上一条定理是用来做那道 open cup 题的,而下一条则是做本题的。
于是我们有一个 naive 算法,倒做耳分解,往一个空图中不断加耳。
令
但是 naive 算法还是 naive 算法,就算能过这题也过不了那个有向图版本,我们进行一个优化。
上面将耳看做了一个整体,但我们不妨将耳逐步加入,具体地,令
那么转移只要每次枚举耳上
时间复杂度为
目前这份代码是本题最优解。
#include <bits/stdc++.h>
using namespace std;
int t,n,m,x,y,z,d[20][20][2],dp[1<<12][14][14][2],f[1<<12];
int main () {
scanf("%d",&t);
for (int ii=1;ii<=t;ii++) {
memset(d,0x2f,sizeof(d));
memset(dp,0x2f,sizeof(dp));
memset(f,0x2f,sizeof(f));
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++) {
scanf("%d%d%d",&x,&y,&z);
x--,y--;
if (z<d[x][y][0]) {d[x][y][1]=d[x][y][0],d[x][y][0]=z;}
else if (z<d[x][y][1]) {d[x][y][1]=z;}
swap(x,y);
if (z<d[x][y][0]) {d[x][y][1]=d[x][y][0],d[x][y][0]=z;}
else if (z<d[x][y][1]) {d[x][y][1]=z;}
}
for (int i=0;i<n;i++) {f[1<<i]=0;}
for (int i=1;i<(1<<n);i++) {
for (int j=0;j<n;j++) {
if (!(i&(1<<j))) {continue;}
for (int k=0;k<n;k++) {
if (!(i&(1<<k))) {continue;}
f[i]=min(f[i],dp[i][j][k][1]+d[j][k][0]);
}
}
if (f[i]<0x2f2f2f2f) {
for (int j=0;j<n;j++) {
if (i&(1<<j)) {continue;}
for (int k=0;k<n;k++) {
if (!(i&(1<<k))) {continue;}
f[i|(1<<j)]=min(f[i|(1<<j)],f[i]+d[j][k][0]+d[j][k][1]);
}
}
for (int j=0;j<n;j++) {
if (!(i&(1<<j))) {continue;}
for (int k=0;k<n;k++) {
if (!(i&(1<<k))) {continue;}
for (int l=0;l<n;l++) {
if (i&(1<<l)) {continue;}
dp[i|(1<<l)][l][k][0]=min(dp[i|(1<<l)][l][k][0],f[i]+d[j][l][0]);
if (j!=k) {f[i|(1<<l)]=min(f[i|(1<<l)],f[i]+d[j][l][0]+d[k][l][0]);}
}
}
}
}
for (int j=0;j<n;j++) {
if (!(i&(1<<j))) {continue;}
for (int k=0;k<n;k++) {
if (!(i&(1<<k))) {continue;}
if (min(dp[i][j][k][0],dp[i][j][k][1])==0x2f2f2f2f) {continue;}
for (int l=0;l<n;l++) {
if (i&(1<<l)) {continue;}
dp[i|(1<<l)][l][k][1]=min(dp[i|(1<<l)][l][k][1],
min(dp[i][j][k][0],dp[i][j][k][1])+d[j][l][0]);
}
}
}
}
if (f[(1<<n)-1]==0x2f2f2f2f) {printf("impossible\n");}
else {printf("%d\n",f[(1<<n)-1]);}
}
return 0;
}