Picnic Planning 【POJ - 1639】【Kruskal+有限度最小生成树】

题目链接


  题意:若干个人开车要去park聚会,但是park能停的车是有限的,为k。所以这些人要通过先开车到其他人家中,停车,然后拼车去聚会。另外,车的容量是无限的,他们家停车位也是无限的。求开车总行程最短。
  就是求一最小生成树,但是对于其中一个点其度不能超过k。


  接下来,就是怎么处理最小生成树的建立问题了,对于这样的树,我们可以看作是除去中间的park节点以外,其他所有的节点先各自构成最小生成树,得到M个联通分块,然后再用M个联通分块与park节点连接,那么,此时park已经用掉了M个度,那么,对于题目要求:一定存在解,就表明了park的限制度是大于等于M的。那么,接下来,我们就得处理多出来的部分(如果有的话)。

  对于多出来的部分,我们有这样的选择:

  • 选择将park与这点连接,并且放弃它们构成的环内的其中一条边,显然,得放弃最大边;
  • 不选择这条边,说明已经到了最优解,没法再放弃其他边了。

  那么,我们要怎么确定要选择的边,与衔接上去的边呢?对于每一个由park节点出发的节点,我们能通过dfs()搜到它如果与park形成环的话,环内部节点的边的最大值,若是把它与park连接起来的话,就是要删除一条最大链,并且增加上它与park的距离。

  我们重复上面的操作,直到找不到合适的值,使得删除树上的点再添加上新的点的时候距离更小。


#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
#define e 2.718281828459045
#define INF 0x3f3f3f3f
#define efs 1e-6
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int maxP = 30;
const int maxN = 1005;
const int park = 1;  //公园一定是1
int N, cnt, limit, root[maxP], sum, mp[maxP][maxP];
bool inTree_line[maxP][maxP];   //这两个点在树上,且直接相连
string s1, s2;
map<string, int> ms;
map<string, bool> sr;
struct Eddge
{
    int u, v, val;
    Eddge(int a=0, int b=0, int c=-1):u(a), v(b), val(c) {}
}edge[maxN], dp[maxN];
bool cmp(Eddge e1, Eddge e2) { return e1.val < e2.val; }
int fid(int x) { return x==root[x]?x:(root[x] = fid(root[x])); }
void Kruskal()
{
    int inque_line = 0;
    for(int i=1; i<=N; i++)
    {
        if(inque_line >= cnt - 2) break;    //去掉的节点是park节点
        if(edge[i].u == park || edge[i].v == park) continue;
        int u = fid(edge[i].u), v = fid(edge[i].v);
        if(u != v)
        {
            root[u] = v;
            inque_line++;
            sum += edge[i].val;
            inTree_line[edge[i].u][edge[i].v] = inTree_line[edge[i].v][edge[i].u] = true;
        }
    }
}
void dfs(int u, int fa)
{
    for(int i=2; i<=cnt; i++)
    {
        if(i == fa || !inTree_line[u][i]) continue;
        if(dp[i].val == -1)
        {
            if(dp[u].val > mp[u][i]) dp[i] = dp[u];
            else dp[i] = Eddge(i, u, mp[u][i]);
        }
        dfs(i, u);
    }
}
void solve()
{
    int key_P[maxP], min_to_root[maxP];   //key_P[]知道,对应连接的每个联通分块中的节点,以及每个联通分块到根的最短距离
    memset(min_to_root, INF, sizeof(min_to_root));
    for(int i=2; i<=cnt; i++)
    {
        if(mp[i][park] < INF)
        {
            int the_R = fid(i);
            if(min_to_root[the_R] > mp[i][park])
            {
                min_to_root[the_R] = mp[i][park];
                key_P[the_R] = i;
            }
        }
    }
    int M = 0;  //M个联通分块的处理
    for(int i=2; i<=cnt; i++)
    {
        if(min_to_root[i] < INF)
        {
            sum += min_to_root[i];
            M++;
            inTree_line[key_P[i]][park] = inTree_line[park][key_P[i]] = true;
        }
    }
    for(int i=M+1; i<=limit; i++)
    {
        memset(dp, -1, sizeof(dp));     //dp处理的是最长路径,然后比对最长路以及替换到根节点,是否可以减少需求
        dp[park].val = -INF;   //不能选择根节点,所以赋最小值
        dfs(park, -1); //从根节点出发
        int Minn = -INF, pos = 0;
        for(int j=2; j<=cnt; j++)
        {
            if(Minn < dp[j].val - mp[j][park])
            {
                Minn = dp[j].val - mp[j][park];
                pos = j;
            }
        }
        if(Minn <= 0) break;
        inTree_line[park][pos] = inTree_line[pos][park] = true;
        inTree_line[dp[pos].u][dp[pos].v] = inTree_line[dp[pos].v][dp[pos].u] = false;
        sum -= Minn;
    }
}
void init()
{
    sum = 0;
    cnt = 1;
    ms.clear(); sr.clear();
    ms["Park"] = 1;
    sr["Park"] = true;
    for(int i=1; i<maxP; i++) root[i] = i;
    memset(mp, INF, sizeof(mp));
    memset(inTree_line, false, sizeof(inTree_line));
}
int main()
{
    while(scanf("%d", &N)!=EOF)
    {
        init();
        for(int i=1; i<=N; i++)
        {
            cin>>s1>>s2;
            if(!sr[s1]) ms[s1] = ++cnt; //cnt记录了最后有多少人
            if(!sr[s2]) ms[s2] = ++cnt;
            sr[s1] = sr[s2] = true;
            int e1;
            scanf("%d", &e1);
            mp[ms[s1]][ms[s2]] = mp[ms[s2]][ms[s1]] = e1;
            edge[i] = Eddge(ms[s1], ms[s2], e1);
        }
        scanf("%d", &limit);
        sort(edge+1, edge+1+N, cmp);
        Kruskal();
        solve();
        printf("Total miles driven: %d\n", sum);
    }
    return 0;
}
/*
12
Park 4 84
Park 3 85
Park 6 11
Park 2 64
Park 5 57
Park 1 40
5 6 64
1 6 83
2 4 70
4 3 26
3 1 55
6 3 56
3
Accept ans = 259
*/

 

后面有一组测试数据,过了这个就基本是过了的,还有,一会我再用Prime写一遍,挺好的一种想法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wuliwuliii

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值