tyvj2054 四叶草魔杖——连通块 & 状压DP

题目:http://www.joyoi.cn/problem/tyvj-2054

把点分成几个连通块,和为0的几个点放在一块,在块内跑最小生成树作为这个块的代价;

然后状压DP,组成全集的最小代价就是答案;

1A了好高兴!

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,a[20],hd[20],ct,f[1<<20],cnt,fa[20],inf=0x3f3f3f3f,g[1<<20],tot;
bool vis[20];
struct N{
    int u,v,w;
    N(int t=0,int n=0,int w=0):u(t),v(n),w(w) {}
}e[205];
bool cmp(N x,N y){return x.w<y.w;}
int calc(int g)
{
    int sum=0;
    for(int i=1;i<=n;i++) if(g&(1<<(i-1)))sum+=a[i];
    return sum;
}
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
int kruskal(int g)
{
    int num=0,ret=0;
    memset(vis,0,sizeof vis);
    for(int i=1;i<=n;i++)
    {
        fa[i]=i;
        if(g&(1<<(i-1)))vis[i]=1,num++;
    }
    int t=0;
    for(int i=1;i<=m;i++)
    {
        if(!vis[e[i].u]||!vis[e[i].v])continue;
        int u=find(e[i].u),v=find(e[i].v);
        if(u!=v)
        {
            fa[u]=v; ret+=e[i].w; t++;
            if(t==num-1)break;
        }
    }
    if(t!=num-1)return inf;
    return ret;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=m;i++) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w),e[i].u++,e[i].v++;
    sort(e+1,e+m+1,cmp);
    memset(f,0x3f,sizeof f);
    for(int i=1;i<=(1<<n)-1;i++)
        if(calc(i)==0)
        {
            int ff=kruskal(i);
            if(ff!=inf)g[++tot]=i,f[i]=ff;
        }
    f[0]=0;
    for(int i=0;i<=(1<<n)-1;i++)
    {
        for(int j=1;j<=tot;j++)
            if((i&g[j])==0)f[i|g[j]]=min(f[i|g[j]],f[i]+f[g[j]]);
    }
    if(f[(1<<n)-1]==inf)printf("Impossible\n");
    else printf("%d\n",f[(1<<n)-1]);
    return 0;
}

 

posted @ 2018-07-07 13:07  Zinn  阅读(221)  评论(0编辑  收藏  举报