【BZOJ3590】Quare(SNOI2013)-状压DP
测试地址:Quare
题目大意:给定一张无向图,有个点和条边,要从里面选出一个边权和最小的包含所有点的边双连通子图,求最小的边权和。
做法:本题需要使用状压DP。
注意到,每一个边双连通分量都可以通过删边成为一个环,而边权都是非负的,所以最优解一定是许多个环接在一起,因此我们可以用一个边双连通分量加上一条链构成一个新的边双连通分量。具体来说,我们要求:
:包含点集的边双连通分量的最小边权和。
:一个不属于点集的点到点集中边的最小值和次小值。
:包含点集的两端为点的链的最小边权和。
具体求的步骤如下:
直接枚举即可。
的话,可以枚举一个集合,在集合中枚举两个点,再枚举能到达的点,用更新。用邻接表可以做到。
最后就是求了,上面说了,可以用一个边双连通分量加上一条链构成一个新的边双连通分量,因此用枚举子集的状压DP做即可,即每次枚举补集内的两个点,用端点为这两个点的,包含某个补集的子集的链,加上这两个点与点集的最小边来更新。注意如果枚举的两个点重合,那么我们需要用这个点和点集的最小边和次小边来更新。这就是和的用处了。这一步的时间复杂度为,其实常数很小,再稍微卡卡常就能过了。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf=1000000000ll*1000000000ll;
int T,n,m,first[20]={0},tot=0;
ll h[15][5010][2],g[5010][15][15],f[5010];
struct edge
{
int u,v,next;
ll d;
}e[100];
void insert(int a,int b,ll d)
{
e[++tot].u=a;
e[tot].v=b;
e[tot].next=first[a];
e[tot].d=d;
first[a]=tot;
}
int num(int x)
{
int sum=0;
while(x)
{
if (x&1) sum++;
x>>=1;
}
return sum;
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
memset(first,0,sizeof(first));
tot=0;
for(int i=1;i<=m;i++)
{
int x,y;
ll d;
scanf("%d%d%lld",&x,&y,&d);
insert(x,y,d),insert(y,x,d);
}
for(int i=0;i<(1<<n);i++)
for(int j=1;j<=n;j++)
{
h[j][i][0]=h[j][i][1]=inf;
if ((1<<(j-1))&i) continue;
for(int k=first[j];k;k=e[k].next)
if ((1<<(e[k].v-1))&i)
{
if (h[j][i][0]>e[k].d)
{
h[j][i][1]=h[j][i][0];
h[j][i][0]=e[k].d;
}
else h[j][i][1]=min(h[j][i][1],e[k].d);
}
}
for(int i=0;i<(1<<n);i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
g[i][j][k]=inf;
for(int i=1;i<=n;i++)
g[1<<(i-1)][i][i]=0;
for(int i=1;i<=tot;i++)
{
int u=e[i].u,v=e[i].v,s=(1<<(u-1))|(1<<(v-1));
g[s][u][v]=min(g[s][u][v],e[i].d);
}
for(int i=1;i<(1<<n);i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
if (((1<<(j-1))&i)&&((1<<(k-1))&i))
{
for(int t=first[k];t;t=e[t].next)
{
if (!((1<<(e[t].v-1))&i))
{
int nxt=i|(1<<(e[t].v-1));
g[nxt][j][e[t].v]=min(g[nxt][j][e[t].v],g[i][j][k]+e[t].d);
}
}
}
for(int i=1;i<=n;i++)
f[1<<(i-1)]=0;
for(int i=1;i<(1<<n);i++)
if (num(i)>1)
{
f[i]=inf;
for(int j=((i-1)&i);j>0;j=((j-1)&i))
for(int k=1;k<=n;k++)
if ((1<<(k-1))&j)
{
for(int l=1;l<=n;l++)
if ((1<<(l-1))&j)
{
if (k==l) f[i]=min(f[i],f[i-j]+g[j][k][k]+h[k][i-j][0]+h[k][i-j][1]);
else f[i]=min(f[i],f[i-j]+g[j][k][l]+h[k][i-j][0]+h[l][i-j][0]);
}
}
}
if (f[(1<<n)-1]==inf) printf("impossible\n");
else printf("%lld\n",f[(1<<n)-1]);
}
return 0;
}