[Snoi2013]Quare
题意
4.20四川芦山地震发生后,抗震救灾委员会接到一个紧急任务,四川省给该委员会发了一份地图,这份地图给出了该省一些城市的情况:任两个城市是用一条或多条公路连接起来的,也可以没有公路连接,但是每个城市都可以直接或间接地到达另外的城市,注意这些公路是可以双向行驶的。由于最近余震、暴雨造成泥石流倾泻,使得车辆在这些公路上行驶很不安全,于是四川省决定尽快对部分公路进行抢修,以保障救援车辆行车安全。
该省对所有的公路情况都进行了勘察,分析估计了抢修某段公路所需要花费的时间,并记录在地图中。现在该省希望抗震救灾委员会能找到一个方案,该方案决定出哪些公路需要抢修,使得抢修后的公路仍能保证任意两个城市之间都能直接或间接地相连,同时为了安全起见,即使某一条抢修的公路被泥石流阻断了,任意两城市仍能保持这个性质。由于时间紧迫,抗震救灾委员会还需保证找到的这个方案总抢修时间最短。
\(n \leq 12, m \leq 40\)
分析
参照Lethelody的题解。
这道题大意就是:给出一个无向图.求一个权值最小的包含所有点的双联通子图.
定义一些状态:
\(f[i]\):集合状态为\(i\).且使在\(i\)中的点双联通的最小权值.
\(h[i][j][0]\):一个端点是\(j\).另一个端点在点集\(i\)中的边的最小权值.
\(h[i][j][1]\):一个端点是\(j\).另一个端点在点集\(i\)中的边的次小权值.
\(g[i][j][k]\).集合状态为\(i\).且使在\(i\)中的点构成一条链.两端点分别是\(j\)和\(k\)的最小权值.
容易发现.一个边双联通图.必然是由一个双联通的子图和一条链,链的两个端点连向这个连通子图构成的.那么就可以枚举这条链进行转移.
要注意的是:一个点是一个权值为0的双联通图.同时也是一条权值为0的链.
在转移的时候.如果这条链是一个点.转移方程是:
f[i] = min(f[i], f[t] + g[s][u][u] + h[t][u][0] + h[t][u][1]);
如果这条链的两端点不同.转移方程是:
f[i] = min(f[i], f[t] + g[s][u][v] + h[t][u][0] + h[t][v][0]);
先预处理出\(h\)和\(g\)数组就好了.
关于\(h\)数组的处理.直接暴力枚举就可以了.
关于\(g\)数组的处理.也是一个状压DP.枚举当前点集i能转移出的状态更新就好了.
时间复杂度\(O(3^n \cdot n^2)\)
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<algorithm>
#include<cstring>
#include<cassert>
#define rg register
#define il inline
#define co const
template<class T>T read()
{
T data=0;
int w=1;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-')
w=-1;
ch=getchar();
}
while(isdigit(ch))
{
data=data*10+ch-'0';
ch=getchar();
}
return data*w;
}
template<class T>T read(T&x)
{
return x=read<T>();
}
using namespace std;
typedef long long ll;
co int INF=0x1f1f1f1f; // edit 2
co int MAXN=12,MAXM=40;
int n,m;
struct edge
{
int nx,fr,to,w;
}e[MAXM<<1]; // edit 1
int head[MAXN],ecnt;
void addedge(int x,int y,int w)
{
e[++ecnt].fr=x,e[ecnt].to=y,e[ecnt].w=w;
e[ecnt].nx=head[x],head[x]=ecnt;
// cerr<<"ecnt="<<ecnt<<endl;
// assert(-1<=e[ecnt].nx&&e[ecnt].nx<=ecnt);
}
int g[1<<MAXN][MAXN][MAXN],h[1<<MAXN][MAXN][2];
int f[1<<MAXN];
void init()
{
memset(g,0x1f,sizeof g);
for(int i=0;i<n;++i)
g[1 << i][i][i]=0;
for(int i=0;i<=ecnt;++i)
{
int x=e[i].fr,y=e[i].to,w=e[i].w;
int s = (1 << x) | (1 << y);
g[s][x][y] = min(g[s][x][y],w);
}
for(int i=1;i<(1<<n);++i)
for(int x=0;x<n;++x)
for(int y=0;y<n;++y)
if(((1 << x) | i) == i && ((1 << y) | i) == i)
for(int k=head[y];k!=-1;k=e[k].nx)
{
assert(-1<=k&&k<=ecnt);
int v=e[k].to,w=e[k].w;
if(((1 << v) | i) != i)
{
int s = (1 << v) | i;
// cerr<<"v="<<v<<endl;
// assert(s<(1<<n));
// assert(s<(1<<n)&&x<n&&v<n&&i<(1<<n)&&y<n);
g[s][x][v] = min(g[s][x][v],g[i][x][y] + w);
}
}
// cerr<<"g end"<<endl;
memset(h,0x1f,sizeof h);
for(int i=1;i<(1<<n);++i)
for(int x=0;x<n;++x)
if(((1 << x) | i) != i)
for(int j=head[x];j!=-1;j=e[j].nx)
{
int y=e[j].to,w=e[j].w;
if(((1 << y) | i) == i)
{
if(w<h[i][x][1])
{
h[i][x][1]=w;
if(h[i][x][1]<h[i][x][0])
swap(h[i][x][1],h[i][x][0]);
}
}
}
}
int cnt(int x)
{
int res=0;
while(x)
{
++res;
x-=(x&-x);
}
return res;
}
void solve()
{
memset(f,0x1f,sizeof f);
for(int i=0;i<n;++i)
f[1 << i] = 0;
for(int i=1;i<(1<<n);++i)
if(cnt(i)>=2)
for(int s=i&(i-1);s;s=(s-1)&i)
{
int t = i - s;
for(int x=0;x<n;++x)
for(int y=0;y<n;++y)
if(((1 << x) | s) == s && ((1 << y) | s) == s)
{
if(x == y)
f[i] = min(f[i],f[t] + g[s][x][x] + h[t][x][0] + h[t][x][1]);
else
f[i] = min(f[i],f[t] + g[s][x][y] + h[t][x][0] + h[t][y][0]);
}
}
}
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
int T=read<int>();
while(T--)
{
memset(head,-1,sizeof head);
ecnt=-1;
read(n);read(m);
for(int i=0;i<m;++i)
{
int x=read<int>()-1,y=read<int>()-1,w=read<int>();
// cerr<<"x="<<x<<" y="<<y<<" w="<<w<<endl;
addedge(x,y,w);
addedge(y,x,w);
}
init();
// cerr<<"init end"<<endl;
solve();
if(f[(1 << n) - 1] < INF)
printf("%d\n",f[(1 << n) - 1]);
else
puts("impossible");
}
return 0;
}
Hint
注意初始值,如果赋成0x3f3f3f3f
加4个就会炸int。
静渊以有谋,疏通而知事。