滑雪与时间胶囊

题目大意:

一个孩子去滑雪,他想要用最短距离访问最多景点,题目会给出M条路径和N个景点。景点是路径的交点。这个孩子还有时间胶囊,时间胶囊可以让他直接转移到上次访问过的景点,问:他从第一个点开始滑,在访问最多景点的条件下,最短路径是多少?


这道题不能用 prim 算法去做,原来 prim 是有缺陷的,它针对无向图是正确的,并不能保证有向图一定正确。

比如说

3 3

3 2 1

1 2 7

2 3 1

1 3 5

这样的话用prim得出的结论是5+7=12,而实际上,我们可以先到2号点,再从二号到3号。这样答案就是最优解7+1=8;故若采用最小生成树方法时,便应当考虑kruskal算法。

思路是这样的:
先用bfs()遍历完所有可能遍历到的节点,标记能遍历到的,然后对原式进行排序先从去的高处向下排,再按路径长度按从小到大排序,然后就套用Kruskal模板。
为什么要用bfs()先遍历呢?这是因为或许会出现一种情况:一条边的两端不可以被遍历到,但是这条边很短,且终点很高,容易被Kruskal添加进去,故要避免这种情况。
先用bfs()遍历,能被遍历的节点被标记为need=true;添加边时应当使两端顶点need都为true;

#include<list>
#include<queue>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>

using namespace std;

struct place//景点结点
{
    int from;
    int to;
    int disten;
};
struct rec//辅助bfs()
{
    int to;
    int weight;
    rec* next;
};
rec* vec[100001];
const int inf = 0x3f3f3f3f;
bool need[100001];
bool used[100001];
int hei[100001];
place p[2000001];
int parent[100001];
int n, m;
int coun;//统计遍历的边数 [0,coun)
int reach;

void addedge(int f,int t,int w)
{
    p[coun++]=place{f,t,w};
    rec *l=new rec;
    l->to=t;
    l->weight=w;
    l->next=vec[f];
    vec[f]=l;
}
bool com(place a, place b)
{
    if (hei[a.to] == hei[b.to])
        return a.disten < b.disten;
    else
        return hei[a.to]>hei[b.to];
}
void bfs()  //遍历所有可能到达的结点
{
    queue<int>q;
    q.push(1);
    need[1]=true;
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
           if(++reach==n)  return ;
        for( rec* it=vec[t]; it; it=it->next)
        {
            if(need[it->to]==false)
            {
                need[it->to]=true;
                q.push(it->to);
            }
        }
    }
}

int getpare(int a)//parent【a】=getparent(parent【a】),这句话会很好的提高效率,加快访问到根节点的时间
{
    return (parent[a] == a) ? a : parent[a]=getpare(parent[a]);
}


void  kruskal()//最小生成树
{
    long long dis = 0;
    sort(p, p + coun, com);//排序的原则是去的最高点优先,然后是路径长度
    for (int q = 1; q <= n; ++q)
        parent[q] = q;
    for (int i = 0; i<coun; ++i)
    {
        if (need[p[i].from]&&need[p[i].to])
        {
            int p1 = getpare(p[i].from);
            int p2 = getpare(p[i].to);
            if (p1 != p2)
            {
                dis += p[i].disten;
                parent[getpare(p[i].from)]=getpare(p[i].to);
            }
        }
    }
    printf("%lld\n",dis);
}
int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i)  scanf("%d", &hei[i]);
    reach = 0;
    coun = 0;
    int  f, t, w;
    for (int i = 1; i <= m; ++i)
    {
        scanf("%d%d%d", &f, &t, &w);
        if (hei[f] >= hei[t])
        {
            addedge(f,t,w);
        }
        if(hei[f]<=hei[t])
        {
            addedge(t,f,w);
        }
    }
    bfs();
    printf("%d ",reach);
    kruskal();
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值