【HDU3311】Dig The Wells-状压DP+SPFA:斯坦纳树
测试地址:Dig The Wells
题目大意:有个寺庙和个其他地点,每个寺庙或者地点都可以挖井,现在要修建一些道路,使得每一个寺庙都至少与一个井连通,挖井和修路都需要费用,求最小费用。
做法:本题需要用到状压DP+SPFA。
什么是斯坦纳树?在一个图中,连通一个关键点集并使得所选边权和最小的边集构成一棵树,这就是斯坦纳树。
斯坦纳树可以这样求:令为选择点为树根,和关键点连通的状态为的最小费用,状态转移方程有两个部分:
第一个部分是,其中是的一个子集。这一个部分是用以自己为根的连通状态组合来更新的答案。这一部分显然可以用枚举子集的状压DP做到。
第二个部分是,其中为点和点的距离。这一个部分是用以其他点为根的相同连通状态来更新答案。我们发现这个方程很像最短路算法中的“松弛”操作,因此我们用SPFA来进行松弛,那么这一个部分的时间复杂度为,其中是SPFA算法的常数。
那么这道题基本上就是这样做的,然而还是有不同,这道题可以有很多不同的连通块,所以我们可以先按照上面的方法DP,然后求出,表示连通中的这些点所需要的最小费用,这里的费用包括打井的费用,接着令为选了个连通块,已选点的集合为的最小费用,则有:
这个显然可以用枚举子集的状压DP算出,那么最后就是所求的答案。
我傻逼的地方:在用表示无穷大的时候,一定要记得确认将要进行的运算中没有包含这种无穷大的数字……WA了好几发……
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,p;
int first[1010],tot,st[50],num[50];
ll val[1010],dp[1010][50],d[2][50],mn[50];
bool vis[1010]={0};
queue<int> Q;
struct edge
{
int v,next;
ll w;
}e[10010];
void insert(int a,int b,ll w)
{
e[++tot].v=b;
e[tot].next=first[a];
e[tot].w=w;
first[a]=tot;
}
bool cmp(int a,int b)
{
return num[a]<num[b];
}
void init()
{
for(int i=1;i<=n+m;i++)
scanf("%lld",&val[i]);
memset(first,0,sizeof(first));
tot=0;
for(int i=1;i<=p;i++)
{
int a,b;
ll w;
scanf("%d%d%lld",&a,&b,&w);
insert(a,b,w),insert(b,a,w);
}
for(int i=0;i<(1<<n);i++)
for(int j=1;j<=n+m;j++)
dp[j][i]=-1;
for(int i=n+1;i<=n+m;i++)
dp[i][0]=0;
for(int i=0;i<(1<<n);i++)
st[i]=i;
sort(st,st+(1<<n),cmp);
}
void spfa(int state)
{
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (dp[e[i].v][state]==-1||dp[e[i].v][state]>dp[v][state]+e[i].w)
{
dp[e[i].v][state]=dp[v][state]+e[i].w;
if (!vis[e[i].v]) vis[e[i].v]=1,Q.push(e[i].v);
}
vis[v]=0;
}
}
void work()
{
for(int i=1;i<(1<<n);i++)
{
int x=st[i];
for(int j=1;j<=n+m;j++)
{
dp[j][x]=-1;
if (j<=n&&x==(1<<(j-1))) dp[j][x]=0;
for(int k=x;k;k=(k-1)&x)
if (dp[j][k]!=-1&&dp[j][x-k]!=-1)
{
if (dp[j][x]==-1) dp[j][x]=dp[j][k]+dp[j][x-k];
dp[j][x]=min(dp[j][x],dp[j][k]+dp[j][x-k]);
}
if (dp[j][x]!=-1) Q.push(j),vis[j]=1;
}
spfa(x);
mn[x]=-1;
for(int j=1;j<=n+m;j++)
if (dp[j][x]!=-1)
{
if (mn[x]==-1) mn[x]=dp[j][x]+val[j];
mn[x]=min(mn[x],dp[j][x]+val[j]);
}
}
int now=1,past=0;
for(int i=0;i<(1<<n);i++)
d[past][i]=-1;
d[past][0]=0;
for(int i=1;i<=n;i++)
{
for(int j=0;j<(1<<n);j++)
{
d[now][j]=d[past][j];
for(int k=j;k;k=(k-1)&j)
if (d[past][j-k]!=-1&&mn[k]!=-1)
{
if (d[now][j]==-1) d[now][j]=d[past][j-k]+mn[k];
d[now][j]=min(d[now][j],d[past][j-k]+mn[k]);
}
}
swap(now,past);
}
printf("%lld\n",d[past][(1<<n)-1]);
}
int main()
{
for(int i=0;i<=45;i++)
{
num[i]=0;
int x=i;
while(x)
{
num[i]++;
x-=(x&(-x));
}
}
while(scanf("%d%d%d",&n,&m,&p)!=EOF)
{
init();
work();
}
return 0;
}