bzoj4774: 修路

Description

村子间的小路年久失修,为了保障村子之间的往来,法珞决定带领大家修路。对于边带权的无向图 G = (V, E),
请选择一些边,使得1 <= i <= d, i号节点和 n - i + 1 号节点可以通过选中的边连通,最小化选中的所有边
的权值和。

Input

第一行两个整数 n, m,表示图的点数和边数。接下来的 m行,每行三个整数 ui, vi, wi,表示有一条 ui 与 vi 
之间,权值为 wi 的无向边。
1 <= d <= 4
2d <= n <= 10^4
0 <= m <= 10^4
1 <= ui, vi <= n
1 <= wi <= 1000

Output

一行一个整数,表示答案,如果无解输出-1
将要求联通的2d个点及其子集求一次斯坦纳树,最后再用状压dp得出最小权值和
#include<cstdio>
#include<cstring>
#include<queue>
const int N=10007,inf=0x3f3f3f3f;
int n,m,d,mx;
int es[N*2],enx[N*2],ev[N*2],e0[N],ep=2;
int f[256][N],g[16];
struct node{
    int w,l;
    bool operator<(const node&x)const{return l>x.l;}
};
std::priority_queue<node>q;
void mins(int&a,int b){if(a>b)a=b;}
int main(){
    scanf("%d%d%d",&n,&m,&d);
    for(int i=0,a,b,c;i<m;++i){
        scanf("%d%d%d",&a,&b,&c);
        es[ep]=b;enx[ep]=e0[a];ev[ep]=c;e0[a]=ep++;
        es[ep]=a;enx[ep]=e0[b];ev[ep]=c;e0[b]=ep++;
    }
    mx=1<<d;
    memset(f,0x3f,sizeof(f[0])*mx*mx);
    memset(g,0x3f,sizeof g);
    for(int i=1;i<=d;++i)f[1<<i-1][i]=f[1<<i+d-1][n+1-i]=0;
    for(int i=1;i<mx*mx;++i){
        int*l=f[i];
        for(int j=i-1&i;j>(i^j);j=j-1&i){
            for(int w=1,*l1=f[j],*l2=f[i^j];w<=n;++w)mins(l[w],l1[w]+l2[w]);
        }
        for(int w=1;w<=n;++w)if(l[w]<inf)q.push((node){w,l[w]});
        while(!q.empty()){
            node w=q.top();q.pop();
            if(w.l!=l[w.w])continue;
            for(int e=e0[w.w];e;e=enx[e]){
                int u=es[e],d=w.l+ev[e];
                if(l[u]>d)q.push((node){u,l[u]=d});
            }
        }
        if((i&mx-1)==(i>>d)){
            int ml=inf;
            for(int j=1;j<=n;++j)mins(ml,l[j]);
            mins(g[i>>d],ml);
        }
    }
    for(int i=1;i<mx;++i){
        for(int j=i-1&i;j;j=j-1&i)mins(g[i],g[j]+g[i^j]);
    }
    printf("%d",g[mx-1]!=inf?g[mx-1]:-1);
    return 0;
}
原文地址:https://www.cnblogs.com/ccz181078/p/6574471.html