HDU 3311 Dig The Wells 【斯坦纳树:状压DP+SPFA】

传送门

题意

可以在\(n+m\)个点上打井,可以修\(p\)条路,求前\(n\)个点能取到井水的最小花费是多少。

题解

把打井也转化为修路,即在\(0\)有一口井,然后求通过修路将\(0,1,...,n\)\(n+1\)个点连通的最小费用。
如果是换成连通所有点的话,这个题就是一个最小生成树,可惜换不得。
然而这个部分连通的最小花费也有对应的数据结构:斯坦纳树。这个题就是斯坦纳树的模板题。
关于斯坦纳树的具体做法就不详细说了,就是状压DP+SPFA。

代码

#include <bits/stdc++.h>
#define x first
#define y second
#define pb push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const double PI  = acos(-1.0);
const double eps = 1e-8;
const int inf = 0x3f3f3f3f;
const LL INF  = 0x3f3f3f3f3f3f;
const int N=1e4+10;
const int M=5e4+10;
const int mod=1e9+7;
inline LL gcd(LL a, LL b) { return b ? gcd(b, a % b) : a; }
inline LL lcm(LL a, LL b) { return a * b / gcd(a, b); }
inline LL read(){LL x=0,f=1;char c=getchar();while(c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}while(c>='0'&&c<='9') {x=x*10+c-'0';c=getchar();}return x*f;}
inline LL qpow(LL x,LL k=mod-2,LL m=mod){LL res=1;while(k){if(k&1) res=res*x%m;x=x*x%m;k>>=1;}return res;}
/*=================================================================================================================*/
int n,m,p,state[N];
int head[N],to[M*2],nxt[M*2],val[M*2],tot;
LL f[1010][100];
void add(int u,int v,int w){
    to[++tot]=v;nxt[tot]=head[u];val[tot]=w;head[u]=tot;
}
queue<PII> que;
int vis[1010][100];

void spfa(){
    while(!que.empty()){
        int u=que.front().x,sta=que.front().y;
        vis[u][sta]=0;que.pop();
        for(int i=head[u];i;i=nxt[i]){
            int vsta=sta|state[to[i]];
            if(f[to[i]][vsta]>f[u][sta]+val[i]){
                f[to[i]][vsta]=f[u][sta]+val[i];
                if(!vis[to[i]][vsta]&&sta==vsta){
                    vis[to[i]][vsta]=1;
                    que.push({to[i],vsta});
                }
            }
        }
    }
}

void Solve(){
    for(int i=0;i<=n+m;i++) head[i]=0;
    tot=0;
    for(int i=1,x;i<=n+m;i++){
        scanf("%d",&x);
        add(0,i,x);add(i,0,x);
    }
    for(int i=1,u,v,w;i<=p;i++){
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);add(v,u,w);
    }
    for(int i=0;i<=n+m;i++)for(int j=0;j<(1<<n+1);j++)f[i][j]=INF;
    for(int i=0;i<=n;i++)f[i][state[i]=1<<i]=0;
    for(int i=n+1;i<=m;i++) state[i]=0;
    for(int sta=0;sta<(1<<n+1);sta++){
        for(int i=0;i<=n+m;i++){
            //枚举子集
            for(int sub=sta&(sta-1);sub;sub=(sub-1)&sta)
                f[i][sta]=min(f[i][sta],f[i][sub|state[i]]+f[i][sta-sub|state[i]]);
            if(f[i][sta]!=INF){
                que.push({i,sta});
                vis[i][sta]=1;
            }
        }
        spfa();
    }
    LL ans=INF;
    for(int i=0;i<=n+m;i++) ans=min(ans,f[i][(1<<n+1)-1]);
    printf("%lld\n",ans);
}

int main(){
    while(~scanf("%d%d%d",&n,&m,&p)) Solve();
    
    return 0;
}

posted @ 2020-09-10 12:54  BakaCirno  阅读(115)  评论(0编辑  收藏  举报