题解:P5776 [SNOI2013] Quare
题意
给你一张有重边、无自环的无向正权图,求出它边权和最小的边双连通生成子图的边权和。
思路简述
感觉截至
关于耳分解的定义,网络上能搜集到多种版本,下文中使用“将一张图不断删去耳(包括所有边和除两端外的所有点),使得最后仅剩环
考虑思考其逆过程,分为两部分,找到
令
我们会有以下几种转移(注意转移顺序,
对于一开始找
但我们发现上述转移过程实际上是存在问题的,对于一个二元环,两点间的最小边会被重复经过。解决方案是预处理两点间的最小边和次小边,特判二元环。此外,在转移
至此本题解决完毕。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353;
int n,m,e[12][12][2],f[(1<<12)+5],dp[(1<<12)+5][12][12][2];
inline bool isin(int msk,int p){return (msk>>p)&1;}
inline void solve(){
memset(e,0x1f,sizeof(e));
memset(f,0x1f,sizeof(f));
memset(dp,0x1f,sizeof(dp));
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
u--,v--;
if(e[u][v][0]>w) e[u][v][1]=e[u][v][0],e[u][v][0]=w;
else if(e[u][v][1]>w) e[u][v][1]=w;
swap(u,v);
if(e[u][v][0]>w) e[u][v][1]=e[u][v][0],e[u][v][0]=w;
else if(e[u][v][1]>w) e[u][v][1]=w;
}
//找G0环部分
for(int s=0;s<n;s++)
for(int t=0;t<n;t++)//特判二元环
if(s!=t) f[(1<<s)|(1<<t)]=min(f[(1<<s)|(1<<t)],e[s][t][0]+e[s][t][1]);
for(int s=0;s<n;s++)//枚举G0环的起点(起点即终点)
for(int k=0;k<n;k++)//走一步
dp[(1<<s)|(1<<k)][k][s][1]=e[s][k][0];
//加耳部分
for(int msk=1;msk<(1<<n);msk++){
//特判二元环
for(int s=0;s<n;s++)
for(int t=0;t<n;t++)
if(isin(msk,s)&&!isin(msk,t))
f[msk|(1<<t)]=min(f[msk|(1<<t)],f[msk]+e[s][t][0]+e[s][t][1]);
//完成当前的耳/G0环
for(int s=0;s<n;s++)
for(int t=0;t<n;t++)
f[msk]=min(f[msk],dp[msk][s][t][0]+e[s][t][0]);//通过 0/1 维无视二元环
//枚举耳的起点和终点,新开一个耳
for(int s=0;s<n;s++)
for(int t=0;t<n;t++)
if(isin(msk,s)&&isin(msk,t))
dp[msk][s][t][s==t]=min(dp[msk][s][t][s==t],f[msk]);//起点终点相等要注意排除二元环
//在扩张当前的链/G0环
for(int s=0;s<n;s++)
for(int t=0;t<n;t++)
for(int k=0;k<n;k++)//我们要从 s 走到 k
for(int op:{0,1})
if(isin(msk,s)&&isin(msk,t)&&!isin(msk,k)){
int nop=op&(s==t);
dp[msk|(1<<k)][k][t][nop]=min(dp[msk|(1<<k)][k][t][nop],dp[msk][s][t][op]+e[s][k][0]);
}
}
if(f[(1<<n)-1]<1e9) cout<<f[(1<<n)-1]<<'\n';
else cout<<"impossible\n";
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;
cin>>T;
while(T--) solve();
return 0;
}