POJ - 1639 最小限制生成树

各位看到不足之处一定要留言指正啊!!!!!!!!!!!!!!!!!!!

Picnic Planning POJ - 1639

第一开始没发现,这个题的数据太水了,后来不删边也能过,于是第一次提交就A了, 后来写其他题发现算法不对, 才发现被这一题坑了
最小

题目大意

有若干个点, m条路, 给出路的起点,终点,权值,并且有一个点的度数有限制小于等于K, 求这棵树最小的权值和是多少

题目解读

抽象出来就是由限制的最小生成树
限制就是某个结点的度数小于等于K
如何求解此类问题最小K度限制生成树;
具体实现步骤如下

算法顺序

1 首先利用kruskal 算法求出各连通分量的最小生成树权值的和,并记录有边(这个有边指的是该边被选出来)相连的点
2 在计算各连通分量到指定点的最小距离, 并记录谁与指定点相连
结果 构成了最小m度生成树,并求出了值,并且记录了与谁相连接
3 搜索建立初始化动态规划初始值
4 遍历求得最小权值
5 求出最小M+1 度生成树
6 直到k
7 算出从m到k这些生成树中的最小值

代码
#define me(ar) memset(ar,0,sizeof(ar))
const int    INF = 1e8;
//......................................................
const int LEN = 30;
int K;
int cnt;
struct Road
{
    int x,y;
    int weight;
    bool operator <(const Road &a) const
    {
        return weight < a.weight;
    }
} road[LEN*LEN+10];//邻接表存边,Kruskal 算法要用
int Matrix[LEN][LEN];//邻接矩阵
int sign[LEN][LEN];//记录那些边是相连的
int vis[LEN];//记录是否相连
int F[LEN];//并查集所用
int Father[LEN];//由i到i+1度限制生成树需要用动态规划求解,用来状态转移
int Best[LEN];//Best[i] 指的是由当前节点到park这些边中最长边是多少
int Find(int x)//并查集所用Find函数
{
    return x == F[x]?x:F[x] = Find(F[x]);
}
void  Dfs(int x)//Dfs动态规划记忆化搜索
{
//    vis[x] = 1;
    for(int i = 1;i <= cnt; ++i )
    {
        if(sign[i][x]&!vis[i])//如果有边相连并且下一个节点没有被访问
        {
            if(x==0)
            {
               Best[i] = -INF;//与park 直接相连的边不能删除
            }
            else
            {
              Best[i] = max(Best[x],Matrix[x][i]);//状态转移方程
            }
            Father[i] = x;
            vis[i] = 1;
            Dfs(i);
        }
    }
}

int main(void)
{
    int N;
    while(cin>>N)
    {
        //初始化并查集数组
        for(int i = 0;i < LEN; ++i)
            F[i] = i;
        me(sign);//初始化标记数组
        me(vis);
        //初始化邻接矩阵
        for(int i = 0;i < LEN; ++i)
            for(int j = 0;j < LEN; ++j)
             Matrix[i][j] = INF;
        cnt = 0;//用来记录共有多少个节点
        set<string> se;
        map<string,int> ma;//将地点编号
        ma["Park"] = 0;//将park 加入节点
        se.insert("Park");
        string s1,s2;
        int a,b;
        int weight = 0;
        for(int i = 0; i < N; ++i)
        {
            cin>>s1>>s2>>weight;
            if(se.find(s1)!=se.end())
                a = ma[s1];//如果节点已编号,则直接使用
            else
                a = ma[s1] = ++cnt,se.insert(s1);//如果没有编号,编号
            if(se.find(s2)!=se.end())
                b = ma[s2];
            else
                b = ma[s2] = ++cnt,se.insert(s2);
            Matrix[a][b] = Matrix[b][a] = weight;
            road[i].x = a;
            road[i].y = b;
            road[i].weight = weight;
        }
        //cout<<se.size()<<endl;
        cin>>K;//最小k度生成树
        int ans = 0;//kruskal 算法求最小生成树
        sort(road,road+N);
        for(int i = 0;i < N; ++i)
        {
            int x = road[i].x;
            int y = road[i].y;
             weight = road[i].weight;
            if(x==0||y==0)//去除掉park这个点
                continue;
            int xx = Find(x);
            int yy = Find(y);
            if(xx!=yy)
            {
                F[xx] = F[yy];
                ans += weight;
                sign[x][y] = sign[y][x] = 1;
            }
        }
        int Min[LEN];//用来记录每一个最小生成树到park点的最小路径
        for(int i = 0;i < LEN; ++i)
            Min[i] = INF;//初始化
        int index[LEN];//用来记录最小路径的点
        for(int i = 1;i <= cnt; ++i)
        {
            if(Matrix[i][0]<Min[Find(i)])
            {
                Min[Find(i)] = Matrix[i][0];
                index[Find(i)] = i;
            }
        }
        cout<<se.size()<<endl;
        int m = 0;//用来记录除去park点即0点之后共有多少个连通分量
        for(int i = 1;i <= cnt; ++i)
        {
            if(Min[i] != INF)
            {
                ans += Min[i];
                sign[index[i]][0] = sign[0][index[i]] = 1;//将这个最小路径的点与park相连
                m++;
            }
        }
        int MMin = ans;
        for(int i = m + 1; i <= K; ++i)//从m+1到K求最小i度生成树
        {
            me(vis);
            vis[0] = 1;
            Dfs(0);
            int select = -1;//select用来记录选择哪个与park点相连是最小的
            int sum = INF;
            for(int i = 1;i <= cnt; ++i)
            {
                if(!sign[0][i] && Matrix[0][i] != INF)
                {
                    if(Matrix[i][0]-Best[i]<sum)
                    {
                        select  = i;
                        sum = Matrix[i][0]-Best[i];
                    }
                }
            }
            if(select == -1)//如果找不到,就跳出循环
                break;
            ans += sum;
            sign[select][0] = sign[0][select] = 1;
            MMin = min(MMin,ans);
            for(int i = select; i != 0; i = Father[i])
            {
                if(Matrix[Father[i]][i]==Best[select])
                {
                    sign[i][Father[i]] = sign[Father[i]][i] = 0;
                    break;
                }
            }

        }
        printf("Total miles driven: %d\n",MMin);
       // cout<<MMin<<endl;
    }
    return 0;
}

类似题目

Arctic Network POJ - 2349
const int LEN = 500+100;
int S,P;
double Matrix[LEN][LEN];
int sign[LEN][LEN];
int vis[LEN];
int x[LEN];
int y[LEN];
double  dis[LEN];
int Father[LEN];
double  Best[LEN];
void Dfs(int x)
{
//    cout<<1<<endl;
    for(int i = 1; i <= P; ++i)
    {
        if(!vis[i]&&sign[i][x])
        {

            if(x==0)
                Best[i] = -INF;
            else
                Best[i] = max(Best[x],Matrix[x][i]);
            vis[i] = 1;
            Dfs(i);
            Father[i] = x;
        }
    }
}
int main(void)
{
    int N;
    cin>>N;
    while(N--)
    {
        cin>>S>>P;
        me(sign);
        me(vis);
        for(int i = 1; i <= P; ++i)
        {
            scanf("%d %d",&x[i],&y[i]);
        }
        Matrix[0][1] = Matrix[1][0] = 0;
        sign[0][1] = sign[1][0]= 1;
        for(int i = 2; i <= P; ++i)
            Matrix[0][i] = Matrix[i][0] = INF;
        for(int i = 1; i <= P; ++i)
        {
            for(int j = i+1; j <= P; ++j)
            {
                Matrix[i][j] = Matrix[j][i] = sqrt((double)((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j])));
            }
        }
        //prim算法求解
        vis[1] = 1;
        int Left = P - 1;
        for(int i = 1; i <= P; ++i)
        {
            dis[i] = Matrix[1][i];
            Father[i] = 1;
        }
        double Min = INF;
        int index = 0;
        while(Left--)//prim算法
        {
            Min = INF;
            for(int i = 1; i <= P; ++i)
            {
                if(!vis[i]&&dis[i]<Min)
                {
                    Min = dis[i];
                    index = i;
                }
            }
            vis[index] = 1;
            if(Min == INF)
                break;
            sign[index][Father[index]] = sign[Father[index]][index] = 1;
            for(int i = 1; i <= P; ++i)
            {
                if(!vis[i]&&Matrix[index][i]<dis[i])
                {
                    dis[i] = Matrix[index][i];
                    Father[i] = index;
                }
            }
        }
        double Max;
        for(int k = 2; k <= S; ++k)
        {

            me(vis);
            vis[0] = 1;
            Father[0] = -1;
            Dfs(0);
            Max = -INF;
            index = 0;
            for(int i = 1; i <= P; ++i)
            {
                if(Max<Best[i])
                {
                    Max =Best[i];
                    index = i;
                }
            }
            sign[index][0] = sign[0][index] = 1;//将边加进去
            for(int i = index; Father[i] != -1; i = Father[i] )//删边
            {
                if(Max == Matrix[Father[i]][i])
                {
                    sign[i][Father[i]] = sign[Father[i]][i] = 0;
                    break;
                }
            }
        }
        me(vis);
        vis[0] = 1;

//        Father[0] = -1;
        Dfs(0);
        Max = -INF;
        index = 2;
        for(int i = 1; i <= P; ++i)
        {
            if(Max<Best[i])
            {
                Max =Best[i];
                index = i;
            }
        }
        if(Max == -INF)
            cout<<0<<endl;
        else
          printf("%.2f\n",Max);
    }
    return 0;
}

转载于:https://www.cnblogs.com/zzuzxy/p/8542649.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值