二进制枚举+prim buy or build 问题

来源:互联网 发布:linux系统启动不了 编辑:程序博客网 时间:2024/05/17 12:20

描述

WWN公司是一家知名的网络公司。最近他们打算在常州建立自己的通信网络。通信网络是一个覆盖n个终端的网络,显然WWN公司的网络必须把这n个节点连通。并且WWN公司希望建造这样一个网络的代价最小。

建造这样一个网络,WWN公司有两个选择。第一个是直接架设线路。直接架设一个从节点A(x1,y1)到节点B(x2,y2)的费用是expens=(x1-x2)2+(y1-y2)2.第二个方案就是从小的网络运营商那里购买小型的线路网。现在已知有q个小型线路网已经存在可供选择,第i个线路网连接了ci个节点,然后买下这个小网络需要付出wi的代价。

现在WWN公司希望身为程序员的你帮他们决定一下怎样铺设和购买线路能够使总的花费最小。

输入

输入数据首先是n(1<=n<=1000),节点数,每个节点被编号1..n。紧接着是q(0<=q<=8),表示已经存在的小网络数。

接下来是q行,每行描述一个小网络。

第I+1行前两个数是ci和wi,后面紧接着是ci个数,代表这个小网络所含的ci个节点。(没有必要具体知道这些小网络是怎样连接的,难道不是吗?)

最后n行,每行两个整数,xi,yi(0<=xi,yi<=3000),代表每个节点的坐标。

输出

    一行,最小花费。

样例输入

7 3
2 4 1 2
3 3 3 6 7
3 9 2 4 5
0 2
4 0
2 0
4 2
1 3
0 5
4 4

样例输出

17


我的做法: dfs 搜索所有选图情况, 然后每种情况计算费用。  南邮acm上在test 8  超时 。


网上查找别人做法, 发现自己在dfs 和 计算费用方面的效率都有差距:


下面是他人的做法:


首先枚举所有情况, 本题中最多只有8个子图,所以可以用二进制数枚举:

   for(i=0;i<(1<<m);i++)    // 1<<m 为所有可能情况数 。  
        {
            
            for(j=0;j<m;j++)if(i&(1<<j))  位置 j 为1  则此情况中选取了子图 j
            {
                sum+=w[j];
                build(j);                         // 根据子图j的情况将图上的点进行连接。
            }

        }


使用prime 算法时,亦比我的更好。  使用dis数组保存每个点距离 “已选点集”的距离 。 初始化时皆为INT_MAX;

 

在选择子图时, 修改子图中的个点之间的距离g[i][j]=0;


先把节点1 放入已选子集,更新dis 。 然后遍历所有点 ,选取子集外一点j ,使其dis[j] 最小 ,然后将j放入子集, 更新所有点距离子集的距离,直到所有点都放入。


剪枝:  保存最小值, 当 当前费用大于最小值时, 没有必要继续运算。


代码:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define INF 999999999
#define maxn 1010
int map[maxn][maxn];
int g[maxn][maxn];
int num[maxn],w[10];
int n,m;
struct node
{
    int x,y;
}p[maxn];
int get_dis(node a,node b)
{
    return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}
void build(int k)
{
    for(int i=1;i<=n;i++)if(num[i]&(1<<k))
        for(int j=1;j<i;j++)if(num[j]&(1<<k))
            g[i][j]=g[j][i]=0;
}
int prim(int &ans,int &sum)
{
    int dis[maxn],i,j,k,ssum=0;
    bool flag[maxn];
    for(i=0;i<=n;i++)dis[i]=INF,flag[i]=false;;
    dis[1]=0;
    for(i=1;i<=n;i++)
    {
        int min=INF;
        for(j=1;j<=n;j++)
            if(!flag[j]&&dis[j]<min)
{
                min=dis[j],k=j;
if(min==0)
break;
}
        flag[k]=true;
        ssum+=dis[k];
        if(ssum+sum>=ans)
return ssum;
        for(j=1;j<=n;j++)
            if(!flag[j]&&dis[j]>g[k][j])
                dis[j]=g[k][j];
    }
    return ssum;
}
int main()
{
    int i,j,k;
    while(scanf("%d%d",&n,&m)!=-1)
    {
        for(i=0;i<=n;i++)num[i]=0;
        for(i=0;i<m;i++)
        {
            scanf("%d%d",&j,&w[i]);
            while(j--)
            {
                scanf("%d",&k);
                num[k]|=(1<<i);
            }
        }
        for(i=1;i<=n;i++)
            scanf("%d%d",&p[i].x,&p[i].y);
        for(i=1;i<=n;i++)
        {
            for(j=1;j<i;j++)
                map[i][j]=map[j][i]=get_dis(p[i],p[j]);
         //   map[i][i]=0;
        }
       int  ans=INF;
        for(i=0;i<(1<<m);i++)
        {
            int  sum=0;
            for(j=1;j<=n;j++)
            {
                for(k=1;k<j;k++)
                    g[j][k]=g[k][j]=map[j][k];
          //      g[j][j]=0;
            }
            for(j=0;j<m;j++)if(i&(1<<j))
            {
                sum+=w[j];
if(sum>=ans)
break;
                build(j);
            }
if(sum>=ans)
continue;
            sum+=prim(ans,sum);
            ans=min(ans,sum);
        }
        printf("%d\n",ans);
    }
}

原创粉丝点击