五一劳动节快乐加餐(图论补充)------P2573 [SCOI2012]滑雪

飞机票

题意:

a180285 非常喜欢滑雪。他来到一座雪山,这里分布着 mm 条供滑行的轨道和 nn 个轨道之间的交点(同时也是景点),而且每个景点都有一编号(1≤i≤n) 和一高度 hi​。

a180285 能从景点 i滑到景点 j当且仅当存在一条 ii 和 jj 之间的边,且 i的高度不小于j。与其他滑雪爱好者不同,a180285 喜欢用最短的滑行路径去访问尽量多的景点。如果仅仅访问一条路径上的景点,他会觉得数量太少。

于是 a18028 5拿出了他随身携带的时间胶囊。这是一种很神奇的药物,吃下之后可以立即回到上个经过的景点(不用移动也不被认为是 a180285 滑行的距离)。

请注意,这种神奇的药物是可以连续食用的,即能够回到较长时间之前到过的景点(比如上上个经过的景点和上上上个经过的景点)。 现在,a180285站在 11 号景点望着山下的目标,心潮澎湃。他十分想知道在不考虑时间胶囊消耗的情况下,以最短滑行距离滑到尽量多的景点的方案(即满足经过景点数最大的前提下使得滑行总距离最小)。你能帮他求出最短距离和景点数吗?

思路:

这里不细说思路了,贴一下某位佬的思路分享:

题目大意为:在只能从点权大的点到点权小的点(可以相等)的情况下,从1点出发建立一棵尽可能有更多点的最小生成树

显然我们不能直接求最小生成树,因为有些点应为高度原因无法到达。

对于建立出来的图A,由1点开始宽搜,将扩展到的点和边加入一个新图B,所有扩展到的点便是能到达的最多点。

我们再在这个新图上跑Kruskal求最小生成树,求得最短距离。

对于排序部分,为保证有尽可能多的点在最小生成树里,我们按终点的高度为第一关键字从大到小排序,边长为第二关键字从小到大排序;

这样就能保证拓展的点最多,进而再用最小生成树求最短距离。

我认为比较重要的几个点,也是我错的点:

既然只能由高点低点,那么建边就当然是从高点到低点建单向边,这不难理解

建出来的点深搜去找个能到的最远的点,这也不难理解,但是搜索建边呢,可以dfs或者bfs,而且要注意一点是,只要是1点能到的肯定要添加入新图,那么存储原图的方式呢可以有邻接表,或者vector存,然后边权就用map表示,当然邻接表理应是更优的,奈何不会QAQ,所以我选择用平时dfs搜树的存放方式。

还有排序的时候要注意,需要优先按照高度,再按照边长,因为首先确保题意只能由高向低,其次需要保证是最小生成树,所以边权要最低

处理完新图后,可以直接上面跑一遍Kruskal算法,就可以得到最小生成树了~~~
 

具体详细可以看代码:

#include<bits/stdc++.h>
#include<unordered_map>
using namespace std;


const int maxn=1000000;

#define int long long
int tot,n,m,cnt,sum=1;
int f[maxn],h[maxn];
bool vis[maxn];
vector<int>edges[maxn];
map<pair<int ,int >,int >value;


bool cmp(edge a,edge b){
	return h[a.v]==h[b.v]?a.w<b.w:h[a.v]>h[b.v];
}

void add(int i,int j,int w)
{
    e[++tot].w=w;
    e[tot].u=i; e[tot].v=j;
}

int findx(int x)
{
	if(x==f[x]) return x;
	else return f[x]=findx(f[x]);
}

void kruskal()
{
    long long  res=0,cnt=0;

    sort(e+1,e+tot+1,cmp);//排序所有边

    for(int i=1;i<=tot;i++)//从小到大枚举所有边
    {
        int a=e[i].u,b=e[i].v;
        long long w=e[i].w;
        a=findx(a),b=findx(b);
        if(a!=b) //如果a和b不连通,集合里加入这条边
        {
            res+=w;//最小生成树中所有边的和
            f[a]=b;
            cnt++;
            if(cnt>=sum-1) break;
        }
    }
    cout<<res<<endl;
}

void dfs(int x){
    for(int i=0;i<edges[x].size();i++)
    {
        add(x,edges[x][i],value[{x,edges[x][i]}]);
        if(vis[edges[x][i]]==0)
        {
            sum++;
            vis[edges[x][i]]=1;
            dfs(edges[x][i]);
        }
    }
}

signed main()
{
    int i,j,u,v,k;
    cin>>n>>m;
    for(i=1;i<=n;i++)
    {
        cin>>h[i];
        f[i]=i;
    }
    for(i=0;i<m;i++)
    {
        cin>>u>>v>>k;
        if(h[u]>=h[v])
        {
            edges[u].push_back(v);
            if(value[{u,v}]==0) value[{u,v}]=k;
            else value[{u,v}]=min(value[{u,v}],k);
        }
        if(h[u]<=h[v])
        {
            edges[v].push_back(u);
            if(value[{v,u}]==0) value[{v,u}]=k;
            else value[{v,u}]=min(value[{v,u}],k);
        }
    }
    vis[1]=1;dfs(1);
//    for(i=1;i<=tot;i++)
//    {
//        cout<<e[i].u<<" "<<e[i].v<<" "<<e[i].w<<endl;
//    }
    cout<<sum<<" ";
    kruskal();
    return 0;
}

/*

3 3
3 2 3
1 2 1
1 2 10
2 3 4

*/

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值