最小生成树学习小结

一个图的生成树,即找出这个无向连通图的某个边集为一棵树
最小生成树,即使边权和最小(最大生成树同理)

Prim算法

设已加入最小生成树的点集为T
算法思路:每次寻找T所连向的不在T中的点的边中最小的边,将此边和点加入T
实现

int Prim(){
    memset(cost,-1,sizeof cost);
    cost[1]=0;//起始点设谁都可以
    int f=1;
    int ans=0;
    while(fint u=-1;
        for(int i=1;i<=n;i++)
            if(!vis[i]&&cost[i]!=-1&&(cost[i]1))
                u=i;
        if(u==-1)
            break;
        vis[u]=1;
        ans+=cost[u];
        for(int i=head[u];i;i=edge[i].nxt){
            int v=edge[i].v,w=edge[i].w;
            if(cost[v]==-1||wreturn ans;
}

Kruskal算法

算法思路:将边按权值排序,从小到大枚举,若两端点还未联通,则加入此边(用并查集实现)
代码?
看例题1

破圈算法

当新加入的边是(u,v)时,找到当前u到v路径上最大的边,若权值大于新边,则替换之

例题1 POJ 1258 Agri-Net

题意:裸的最小生成树

#include
#include
#include
using namespace std;
int n;
struct Edge{
    int s,e,w;
    Edge(int ss,int ee,int ww):s(ss),e(ee),w(ww){}
    Edge(){}
    bool operator <(const Edge &e1) const{
        return wvector  edge;
vector<int > father;
int getroot(int x){
    if(father[x]==x)
        return x;
    father[x]=getroot(father[x]);
    return father[x];
}
void merge(int x,int y){
    int f1=getroot(x),f2=getroot(y);
    if(f1==f2)
        return ;
    father[f2]=f1;
}
int main(){
    while(~scanf("%d",&n)){
        father.clear();
        edge.clear();
        for(int i=0;ifor(int i=0;ifor(int j=0;jint w;
                scanf("%d",&w);
                edge.push_back(Edge(i,j,w));
            }
        sort(edge.begin(),edge.end());
        int done=0,len=0;
        for(int i=0;iif(getroot(edge[i].s)!=getroot(edge[i].e))
            {
                merge(edge[i].s,edge[i].e);
                done++;
                len+=edge[i].w;
            }
            if(done==n-1)
                break;
        }
        printf("%d\n",len);
    }
}

例题2 POJ 2377 Bad Cowtractors

题意:最大生成树

#include
#include
#include
using namespace std;
int fa[1005];
int find_father(int x){
    if(x==fa[x])
        return x;
    return fa[x]=find_father(fa[x]);
}
struct node{
    int x,y,c;
}edge[20005],p;
bool cmp(node a,node b){
    return a.c>b.c;
}
int down;
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        p.x=a,p.y=b,p.c=c;
        edge[i]=p;
    }
    sort(edge+1,edge+m+1,cmp);
    int ans=0;
    for(int i=1;i<=n;i++)
        fa[i]=i;
    for(int i=1;i<=m;i++){
        p=edge[i];
        int f1=find_father(p.x),f2=find_father(p.y);
        if(f1==f2)
            continue ;
        fa[f1]=f2;
        ans+=p.c;
        down++;
    }
    if(down==n-1)
        printf("%d",ans);
    else
        printf("-1");
}

例题3 POJ 2395 Out of Hay

题意:求最小生成树上最长的边

#include
#include
#include
using namespace std;
int fa[1005];
int find_father(int x){
    if(x==fa[x])
        return x;
    return fa[x]=find_father(fa[x]);
}
struct node{
    int x,y,c;
}edge[20005],p;
bool cmp(node a,node b){
    return a.c>b.c;
}
int down;
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        p.x=a,p.y=b,p.c=c;
        edge[i]=p;
    }
    sort(edge+1,edge+m+1,cmp);
    int ans=0;
    for(int i=1;i<=n;i++)
        fa[i]=i;
    for(int i=1;i<=m;i++){
        p=edge[i];
        int f1=find_father(p.x),f2=find_father(p.y);
        if(f1==f2)
            continue ;
        fa[f1]=f2;
        ans+=p.c;
        down++;
    }
    if(down==n-1)
        printf("%d",ans);
    else
        printf("-1");
}

Er…前三道好像都是版题
那就来个稍微强一点的吧


例题4 POJ 2349 Arctic Network

题意:某地区共有n座村庄,每座村庄的坐标用一对整数(x,y)表示,现在要在村庄之间建立通讯网络。
• 通讯工具有两种,分别是需要铺设的普通线路和无线通讯的卫星设备。
• 只能给k个村庄配备卫星设备,拥有卫星设备的村庄互相间直接通讯。
• 铺设了线路的村庄之间也可以通讯。但是由于技术原因,两个村庄之间线路长度最多不能超过 d
 已知所有村庄的坐标 ( x , y ) ,卫星设备的数量 k 。
 问:如何分配卫星设备,才能使各个村庄之间能直接或间接的通讯,并且 d 的值最小?求出 d 的最小值。
 数据规模:0 <= k <= n<= 500

题解

假设 d 已知,把所有铺设线路的村庄连接起来,构成一个图。需要卫星设备的台数就是图的连通分量的个数
显然d越小,连通分量越多
其实呢,说白了,就是完全图最小生成树第K大的边长
因为:最小生成树中的最长k-1条长边都去掉后,正好将原树分成了k 个连通分支,在每个连通分支上摆一个卫星设备即可,而剩下图中最长长度就是第k大的边
这么说来好像还是版题
突然,一个诡异的问题出现了
一个图可能有多个最小生成树,它们第K大的边难道都一样长?
巧了,它们确实一样长
我们有这样一个结论

一个图的两棵最小生成树,边的权值序列排序后结果相同

什么?你要证明?

我已经发现了这个结论的一个奇妙的证明,由于这里篇幅太小,写不下
——Pierre de Fermat
另解:
我们用度限制最小生成树的办法,新建一个超级点,对每个点连一条权值为0的边,然后限制它度数不大于k


例题5 CF160D Edges in MST

给你一个n(n<=10^5)个点, m条边(n-1<=m <= min(10^5, n*(n-1)/2))的无向连通图(任何两个点之间只有一条边)。给出每条边的两个端点和对应的权值。对于图中的每一条边,判断
(1)存在于任何一颗最小生成树中 any
(2)至少存在于某一颗最小生成树中 at least one
(3)不存在任何一棵最小生成树中 none

题解

假设所有边权值都相同,那么割边是any,其他的边是at least one
假设所有边的权值都不相同,那么我们按着kruscal的方法构造最小生成树即可
那么回到这个问题,其实我们要做的就是把上面的方法综合一下
相同权值的边一起处理,不同权值的边按kruscal来做

#include
#include
#include
using namespace std;
const int N=100005,M=200005;

struct node{
    int u,v,next,pos,rev;
    bool ban;
}edge[M];
struct node1{
    int u,v,w,pos;
}e[M];
int n,m;
int dfn[N],low[N],time;
int ans[M];
int fa[N];
int head[N],cnt;

bool cmp(node1 a,node1 b){
    return a.wint u,int v,int pos,int rev){
    cnt++;
    edge[cnt].u=u;
    edge[cnt].v=v;
    edge[cnt].pos=pos;
    edge[cnt].rev=rev;
    edge[cnt].next=head[u];
    edge[cnt].ban=0;
    head[u]=cnt;
}
int find_father(int x){
    if(x==fa[x])
        return x;
    return fa[x]=find_father(fa[x]);
}
void merge(int x,int y){
    int f1=find_father(x),f2=find_father(y);
    if(f1==f2)
        return ;
    fa[f1]=f2;
}
void tarjan(int u){
    time++;
    dfn[u]=low[u]=time;
    for(int i=head[u];i;i=edge[i].next){
        if(edge[i].ban)
            continue ;
        edge[i].ban=1;
        edge[edge[i].rev].ban=1;
        int v=edge[i].v;
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
            if(dfn[u]pos]!=-2)
                ans[edge[i].pos]=1;
        }
        else
            low[u]=min(low[u],dfn[v]);
    }
}

int main()
{
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        fa[i]=i;
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
        e[i].pos=i;
    }
    sort(e+1,e+m+1,cmp);
    for(int i=1;i<=m;i++){
        cnt=0;
        int j=i;
        while(j<m&&e[j].w==e[j+1].w) j++;
        for(int k=i;k<=j;k++){
            if(find_father(e[k].u)!=find_father(e[k].v)){
                add_edge(fa[e[k].u],fa[e[k].v],e[k].pos,cnt+2);
                add_edge(fa[e[k].v],fa[e[k].u],e[k].pos,cnt);
                ans[e[k].pos]=-1;
            }
        }
        time=0;
        for(int k=i;k<=j;k++){
            if(ans[e[k].pos]!=0&&!dfn[fa[e[k].u]])
                tarjan(fa[e[k].u]);
        }
        for(int k=i;k<=j;k++){
            if(ans[e[k].pos]!=0){
                merge(e[k].u,e[k].v);
                dfn[fa[e[k].u]]=dfn[fa[e[k].v]]=0;
                head[fa[e[k].u]]=head[fa[e[k].v]]=0;
            }
        }
        i=j;
    }
    for(int i=1;i<=m;i++){
        if(ans[i]==1)
            printf("any\n");
        else if(ans[i]==-1)
            printf("at least one\n");
        else
            printf("none\n");
    }
}

例题6 bzoj 1977 次小生成树

题目大意:找到一棵严格的次小生成树。保证存在。

题解

结论:次小生成树一定能由最小生成树替换一条边得到
有了这个结论,我们先跑一遍最小生成树,然后对于不在最小生成树中的边,我们考虑替换它会增长多少,然后对增长量取最小值即可
注意到必须是严格的次小生成树,如果路径上的最长边等于我们将要加入的边,那么我们计算的增加量不应该是0,而应该是这条边减去严格的次大边
所以具体实现上,树上RMQ,储存最大值和严格的次大值

#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int N=100005,M=600005,L=20;
struct node{
    int u,v,w,nxt;
    int pos;
}e[M],edge[M];
int head[N],mcnt;
void add_edge(int u,int v,int w,int pos){
    mcnt++;
    edge[mcnt].u=u;
    edge[mcnt].v=v;
    edge[mcnt].w=w;
    edge[mcnt].pos=pos;
    edge[mcnt].nxt=head[u];
    head[u]=mcnt;
}
int fa[N];
bool tree[M];
bool cmp(node a,node b){
    return a.wint find_father(int x){
    if(fa[x]==x)
        return x;
    return fa[x]=find_father(fa[x]);
}
void merge(int x,int y){
    int f1=find_father(x),f2=find_father(y);
    fa[f1]=f2;
}
ll ans;
int n,m;
void Kruscal(){
    sort(e+1,e+m+1,cmp);
    for(int i=1;i<=n;i++)
        fa[i]=i;
    for(int i=1;i<=m;i++){
        int u=e[i].u,v=e[i].v;
        if(find_father(u)!=find_father(v)){
            merge(u,v);
            ans+=e[i].w;
            tree[e[i].pos]=1;
        }
    }
}
int max1[N][L+2],max2[N][L+2];
int pa[N][L+2];
int deep[N];
bool vis[N];
void dfs(int u,int fa,int d){
    deep[u]=d;
    vis[u]=1;
    for(int i=head[u];i;i=edge[i].nxt){
        int v=edge[i].v,w=edge[i].w;
        if(!tree[edge[i].pos]||v==fa)
            continue ;
        pa[v][0]=u;
        max1[v][0]=w;
        dfs(v,u,d+1);
    }
}
void pre(){
    dfs(1,-1,1);
    for(int i=1;i<=L;i++)
        for(int j=1;j<=n;j++){
            pa[j][i]=pa[pa[j][i-1]][i-1];
            max1[j][i]=max(max1[j][i-1],max1[pa[j][i-1]][i-1]);
            max2[j][i]=max(max2[j][i-1],max2[pa[j][i-1]][i-1]);
            if(max1[j][i-1]>max1[pa[j][i-1]][i-1]&&max1[pa[j][i-1]][i-1]>max2[j][i])
                max2[j][i]=max1[pa[j][i-1]][i-1];
            if(max1[pa[j][i-1]][i-1]>max1[j][i-1]&&max1[j][i-1]>max2[j][i])
                max2[j][i]=max1[j][i-1];
        }
}
int LCA(int x,int y){
    if(deep[x]int i=0;
    while(1<for(int j=i;j>=0;j--)
        if(deep[x]-(1<=deep[y])
            x=pa[x][j];
    if(x==y)
        return x;
    for(int j=L;j>=0;j--)
        if(pa[x][j]!=pa[y][j])
            x=pa[x][j],y=pa[y][j];
    return pa[x][0];
}
int calc(int x,int y,int len){
    int lca=LCA(x,y);
    int i=0;
    while(1<int res=0;
    for(int j=i;j>=0;j--)
        if(deep[x]-(1<=deep[lca]){
            if(max1[x][j]!=len)
                res=max(res,max1[x][j]);
            else
                res=max(res,max2[x][j]);
            x=pa[x][j];
        }
    i=0;
    while(1<for(int j=i;j>=0;j--)
        if(deep[y]-(1<=deep[lca]){
            if(max1[y][j]!=len)
                res=max(res,max1[y][j]);
            else
                res=max(res,max2[y][j]);
            y=pa[y][j];
        }
    return res;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        e[i].u=u,e[i].v=v,e[i].w=w,e[i].pos=i;
        add_edge(u,v,w,i);
        add_edge(v,u,w,i);
    }
    Kruscal();
    pre();
    ll add=1<<30;
    for(int i=1;i<=m;i++)
        if(!tree[e[i].pos]){
            ll ff=calc(e[i].u,e[i].v,e[i].w);
            if(ff>0&&e[i].w-ff>0)
                add=min(add,e[i].w-ff);//
        }
    printf("%lld\n",ans+add);
}

例题7 POJ 1639 Picnic Planning

题目大意:带权无向图,Park节点要求度数必须<=k,求满足这个限制的最小生成树

题解:带度数限制的最小生成树

我们先删掉根节点
再对剩下的图跑最小生成森林
设这个森林有m个联通块,那么根节点的度数至少为m,如果m>k我们可以直接判定不可能
我们将根节点与连接到各个联通块最短的边连起来,我们就得到了一个m度的最小生成树
那么现在我们的问题是,已经得到了一个x度最小生成树,怎么求一个x+1度最小生成树
我们可以枚举每个与根节点相邻而这条边还未加入生成树的边
然后通过dp来计算添加这条边增加的最小权值(即环上最长的边)

#include
#include
#include
#include
#include
#include
using namespace std;
const int N=25,M=1000;
const int INF=0x3f3f3f3f;
struct node{
    int u,v,w;
}e[M],dp[N];
int ecnt;
bool cmp(node a,node b){
    return a.wint fa[N];
bool ontree[N][N];
int G[N][N];
int ans;
int n,m,k;
int find_father(int x){
    if(x==fa[x])
        return x;
    return fa[x]=find_father(fa[x]);
}
void merge(int x,int y){
    int f1=find_father(x),f2=find_father(y);
    fa[f1]=f2;
}
void Kruskal(){
    sort(e+1,e+ecnt+1,cmp);
    for(int i=1;i<=ecnt;i++){
        int u=e[i].u;
        int v=e[i].v;
        if(u==1||v==1) continue ;
        if(find_father(u)!=find_father(v)){
            merge(u,v);
            ontree[u][v]=ontree[v][u]=1;
            ans+=e[i].w;
        }
    }
}
void dfs(int u,int fa){
    for(int v=2;v<=n;v++){
        if(v==fa||!ontree[u][v])
            continue ;
        if(dp[v].w==-1){
            if(dp[u].w>G[u][v])
                dp[v]=dp[u];
            else{
                dp[v].u=u;
                dp[v].v=v;
                dp[v].w=G[u][v];
            }
        }
        dfs(v,u);
    }
}
int minedge[N];
void solve(){
    int point[N];
    for(int i=2;i<=n;i++)
        if(G[1][i]!=INF){
            int color=find_father(i);
            if(minedge[color]>G[1][i]){
                minedge[color]=G[1][i];
                point[color]=i;
            }
        }
    for(int i=1;i<=n;i++)
        if(minedge[i]!=INF){
            m++;
            ontree[1][point[i]]=ontree[point[i]][1]=1;
            ans+=G[1][point[i]];
        }
    for(int i=m+1;i<=k;i++){
        memset(dp,-1,sizeof dp);
        dp[1].w=-INF;
        for(int j=2;j<=n;j++)
            if(ontree[1][j])
                dp[j].w=-INF;
        dfs(1,-1);
        int idx,minnum=INF;
        for(int j=2;j<=n;j++){
            if(minnum>G[1][j]-dp[j].w){
                minnum=G[1][j]-dp[j].w;
                idx=j;
            }
        }
        if(minnum>=0)
            break;
        ontree[1][idx]=ontree[idx][1]=1;
        ontree[dp[idx].u][dp[idx].v]=ontree[dp[idx].v][dp[idx].u]=0;
        ans+=minnum;
    }
}
map<string,int>dict;
int main()
{
    int name;
    scanf("%d",&name);
    string s1,s2;
    memset(minedge,0x3f,sizeof minedge);
    memset(G,0x3f,sizeof G);
    dict["Park"]=++n;
    for(int i=0;ifor(int i=1;i<=name;i++){
        cin>>s1>>s2;
        int d;scanf("%d",&d);
        if(!dict[s1]) dict[s1]=++n;
        if(!dict[s2]) dict[s2]=++n;
        int u=dict[s1],v=dict[s2];
        ecnt++;
        e[ecnt].u=u;
        e[ecnt].v=v;
        e[ecnt].w=d;
        G[u][v]=G[v][u]=min(G[u][v],d);
    }
    scanf("%d",&k);
    Kruskal();
    solve();
    printf("Total miles driven: %d\n",ans);
}

例题8 poj 2728 Desert King

平面上有n个点,每个点有一个h,对于每条边而言,长度为直线距离,费用为h值之差,现在要求找到一个生成树,使得总费用与总长度的比值最小

题解

01分数规划
二分答案为x
则对于

x<=wΔh x <= ∑ w ∑ Δ h

那么也就是
wxΔh>=0 ∑ w − x Δ h >= 0

那么也就是最小生成树>=0即可
两点间的距离和费用最好预处理出来,直接算很有可能会T

#include
#include
#include
#include
#include
using namespace std;
const int N=1005;
const double INF=23333333333333.0;
int x[N],y[N],h[N],n;
double dist[N];
bool vis[N];
double dh[N][N],dd[N][N];
double Prim(double mid){
    for(int i=1;i<=n;i++)
        dist[i]=INF;
    dist[1]=0;
    memset(vis,0,sizeof vis);
    vis[1]=1;
    int u=1;
    double ans=0;
    for(int i=1;idouble mincost=INF;
        int minv;
        for(int v=1;v<=n;v++)
            if(!vis[v]){
                double len=dh[u][v]-mid*dd[u][v];
                if(lenif(dist[v]1;
        u=minv;
    }
    return ans;
}
void pre(){
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++){
            dh[i][j]=fabs(h[i]-h[j]);
            dd[i][j]=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
        }
}
int main()
{
    while(~scanf("%d",&n)&&n){
        for(int i=1;i<=n;i++){
            scanf("%d%d%d",&x[i],&y[i],&h[i]);
        }
        pre();
        double l=0,r=10000000,mid;
        int T=50;
        while(T--){
            mid=(l+r)/2;
            if(Prim(mid)>0){
                l=mid;
            }
            else{
                r=mid;
            }
        }
        printf("%.3f\n",r);
    }
}

例题9 CodeForces 141E Clearing Up

求两种类型的边出现次数相同的生成树

题解

首先显然n必须是奇数
先把两种边分别编号为0、1
然后跑最小生成树,那么现在我们得到的生成树是满足有尽可能多的0边的
若此时生成树的权值已经大于了(n-1)/2,那么就无解了
然后对于每条1边(u,v),我们看加入u到v路径上有没有权值为0的边,如果有,则替换之,然后生成树权值++,重复此操作,至权值为(n-1)/2

#include
#include
#include
#include
using namespace std;
const int M=1e6,N=1e4;
struct node{
    int u,v,w,pos,nxt,rev;
    bool vis;
}e[M],edge[M];
bool cmp(node a,node b){
    return a.wint head[N],mcnt;
bool tree[M];
void add_edge(int u,int v,int w,int pos,int rev){
    mcnt++;
    edge[mcnt].u=u;
    edge[mcnt].v=v;
    edge[mcnt].w=w;
    edge[mcnt].pos=pos;
    edge[mcnt].rev=rev;
    edge[mcnt].nxt=head[u];
    head[u]=mcnt;
}
int fa[N];
int find_father(int x){
    if(x==fa[x])
        return x;
    return fa[x]=find_father(fa[x]);
}
void merge(int x,int y){
    int f1=find_father(x),f2=find_father(y);
    fa[f1]=f2;
}
bool poquan(int u,int t,int& id){
    if(u==t)
        return 1;
    for(int i=head[u];i;i=edge[i].nxt){
        if(edge[i].vis)
            continue ;
        edge[i].vis=1;
        edge[edge[i].rev].vis=1;
        bool ok;
        ok=poquan(edge[i].v,t,id);
        edge[i].vis=0;
        edge[edge[i].rev].vis=0;
        if(ok){
            if(!edge[i].w)
                id=i;
            return 1;
        }
    }
    return 0;
}
int main()
{
    int n,m;
    int mmp;
    scanf("%d%d",&n,&m);
    mmp=m;
    if((n-1)&1){
        puts("-1");
        return 0;
    }
    int sum=(n-1)/2;
    for(int i=1,j=1;i<=m;i++,j++){
        int u,v,w;
        char c;
        scanf("%d%d %c",&u,&v,&c);
        if(c=='S')
            w=1;
        else
            w=0;
        e[i].u=u,e[i].v=v,e[i].w=w,e[i].pos=j;
        if(u==v){
            i--;
            m--;
            continue ;
        }
    }
    sort(e+1,e+m+1,cmp);
    for(int i=1;i<=n;i++)
        fa[i]=i;
    int tot=0,k=0;
    for(int i=1;i<=m&&k1;i++){
        int u=e[i].u,v=e[i].v,w=e[i].w,pos=e[i].pos;
        if(find_father(u)!=find_father(v)){
            merge(u,v);
            k++;
            tot+=w;
            tree[e[i].pos]=1;
            add_edge(u,v,w,pos,mcnt+2);
            add_edge(v,u,w,pos,mcnt);
            e[i].vis=1;
        }
    }
    if(tot>sum){
        puts("-1");
        return 0;
    }
    for(int i=1;i<=m&&totif(e[i].w==0)
            continue ;
        int u=e[i].u,v=e[i].v,pos=e[i].pos;
        if(e[i].vis)
            continue ;
        int id=0;
        poquan(u,v,id);
        if(!id)
            continue;
        tot++;
        tree[e[i].pos]=1;
        tree[edge[id].pos]=0;
        edge[id].vis=1;
        edge[edge[id].rev].vis=1;
        add_edge(u,v,1,pos,mcnt+2);
        add_edge(v,u,1,pos,mcnt);
    }
    if(tot"-1");
        return 0;
    }
    /*find_father(1);
    for(int i=2;i<=n;i++)
        if(find_father(i)!=fa[1]){//!
            printf("-1");
            return 0;
        }*/
    printf("%d\n",n-1);
    for(int i=1;i<=mmp;i++)
        if(tree[i]){
            printf("%d ",i);
        }
}

例题10 斯坦纳树 bzoj 2595 游览计划

题目大意:给出一个n*m的格子,询问使给出的k个点联通的最小花费
(这个是点权)
n,m<=10,k<=min(10,n*m)

题解

dp(i,S)表示当前在i,已连接状态为S的最小花费
转移:
第一种: d(i,S)=min(d(i,x)+d(i,Sx)c(i)) d ( i , S ) = m i n ( d ( i , x ) + d ( i , S − x ) − c ( i ) ) (减去c(i)是因为这是点权,c(i)算重了)
第二种: d(i,S)=min(d(j,Sv[i])+c(i)) d ( i , S ) = m i n ( d ( j , S − v [ i ] ) + c ( i ) ) (v[i]为i号节点状压的值,若i不在那k个点中则为0
枚举子集的技巧:for(sub=(state-1)&state;sub;sub=(sub-1)&state) sub即为state的一个子集
然后呢,第二种转移可以用SPFA来实现
而且这两个转移可以分开计算

#include
#include
#include
#include
using namespace std;
const int M=1025;
const int N=15;
int x[N],y[N];
int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
const int INF=0x3f3f3f3f;
struct Point{
    int x,y;
    Point(){}
    Point(int _x,int _y){
        x=_x;
        y=_y;
    }
}p[N*M];
struct State{
    int s,x,y;
    State(){}
    State(int _s,int _x,int _y){
        s=_s,x=_x,y=_y;
    }
}pre[M][N][N];
int n,m;
int d[M][N][N],mat[N][N];
int k,pcnt,state,S;
bool vis[N][N];
queueq1,q2;
void Init(){
    scanf("%d%d",&n,&m);
    memset(d,0x3f,sizeof d);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            scanf("%d",&mat[i][j]);
            if(!mat[i][j]){
                d[1<0;
                k++;
            }
        }
    S=(1<1;
}
bool cmp(Point a,Point b){
    return d[state][a.x][a.y]void SPFA(){
    Point u;
    sort(p+1,p+pcnt+1,cmp);
    for(int i=1;i<=pcnt;i++){
        q1.push(p[i]);
        vis[p[i].x][p[i].y]=true;
    }
    while((!q1.empty())||(!q2.empty())){
        if(q1.empty())
            u=q2.front(),q2.pop();
        else if(q2.empty())
            u=q1.front(),q1.pop();
        else{
            Point u1=q1.front(),u2=q2.front();
            if(d[state][u1.x][u1.y]else
                u=u2,q2.pop();
        }
        vis[u.x][u.y]=false;
        for(int i=0;i<4;i++){
            int x=u.x+dir[i][0],y=u.y+dir[i][1];
            if(x<=0||y<=0||x>n||y>m)
                continue ;
            if(d[state][x][y]>d[state][u.x][u.y]+mat[x][y]){
                d[state][x][y]=d[state][u.x][u.y]+mat[x][y];
                pre[state][x][y]=State(state,u.x,u.y);
                if(!vis[x][y]){
                    q2.push(Point(x,y));
                    vis[x][y]=true;
                }
            }
        }
    }
}
void Steiner_tree(){
    for(state=1;state<=S;state++){
        pcnt=0;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++){
                for(int s=(state-1)&state;s;s=(s-1)&state){
                    if(d[state][i][j]>d[s][i][j]+d[state-s][i][j]-mat[i][j]){
                        d[state][i][j]=d[s][i][j]+d[state-s][i][j]-mat[i][j];
                        pre[state][i][j]=State(s,i,j);
                    }
                }
                if(d[state][i][j]!=INF){
                    pcnt++;
                    p[pcnt]=Point(i,j);
                }
            }
        SPFA();
    }
}
void dfs(int x,int y,int s){
    if(pre[s][x][y].s==0)
        return ;
    vis[x][y]=true;
    int xx=pre[s][x][y].x,yy=pre[s][x][y].y,ss=pre[s][x][y].s;
    if(xx==x&&yy==y){
        dfs(x,y,ss);
        dfs(x,y,s-ss);
    }
    else
        dfs(xx,yy,ss);
}
void Print(){
    Point u;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(!mat[i][j]){
                u.x=i,u.y=j;
                break;
            }
    printf("%d\n",d[S][u.x][u.y]);
    dfs(u.x,u.y,S);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(!mat[i][j])
                putchar('x');
            else if(vis[i][j])
                putchar('o');
            else
                putchar('_');
        }
        puts("");
    }
}
int main()
{
    Init();
    Steiner_tree();
    Print();
}

你可能感兴趣的:(图论)