[SCOI2012]滑雪 (最小生成树 Kruskal)

题目描述

a180285非常喜欢滑雪。他来到一座雪山,这里分布着M条供滑行的轨道和N个轨道之间的交点(同时也是景点),而且每个景点都有一编号i(1iN)和一高度Hi。a180285能从景点ii滑到景点j当且仅当存在一条i和j之间的边,且i的高度不小于j。 与其他滑雪爱好者不同,a180285喜欢用最短的滑行路径去访问尽量多的景点。如果仅仅访问一条路径上的景点,他会觉得数量太少。于是a180285拿出了他随身携带的时间胶囊。这是一种很神奇的药物,吃下之后可以立即回到上个经过的景点(不用移动也不被认为是a180285 滑行的距离)。请注意,这种神奇的药物是可以连续食用的,即能够回到较长时间之前到过的景点(比如上上个经过的景点和上上上个经过的景点)。 现在,a180285站在11号景点望着山下的目标,心潮澎湃。他十分想知道在不考虑时间胶囊消耗的情况下,以最短滑行距离滑到尽量多的景点的方案(即满足经过景点数最大的前提下使得滑行总距离最小)。你能帮他求出最短距离和景点数吗?

输入格式:

输入的第一行是两个整数N,M。

接下来1行有N个整数Hi,分别表示每个景点的高度。

接下来M行,表示各个景点之间轨道分布的情况。每行3个整数Ui,Vi,Ki。表示编号为Ui的景点和编号为Vi的景点之间有一条长度为Ki的轨道。

输出格式:

输出一行,表示a180285最多能到达多少个景点,以及此时最短的滑行距离总和。

题解:

对于高度不同的两点,他们之间的边是有向的,对于同于高度的点,有边就可以互相到达,我们只需要选取最短的一些边使他们仍联通即可。

若按照正常思路,以边权排序,直接跑Kruskal的话,会发现可能出现有下面的点连接上面的点,如:他会直接选取两条边权为5的边

 

 

 我们考虑如何消除高度的影响,我们以终点高度为第一关键字,边权为第二关键字排序,那么就不可能出现上面的情况,因为他会先选20的边。

相当于选点是逐层进行的,上面的点一定会先被选取,所以这个点选了之后,不可能再连一条向上的边。

接着愉快地打出了代码。

#include<bits/stdc++.h>
using namespace std;
#define ll long long

const int maxn=1000005;
int n,m;
ll ans;
int fa[maxn],h[maxn];
struct edge{
    int x,y;
    ll k;
}a[maxn];

template<class T>inline void read(T &x){
    x=0;char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
}

void swap(ll &x,ll &y){ll t=x;x=y;y=t;}

bool cmp(edge a,edge b){
    if(h[a.y]==h[b.y]) return a.k<b.k;
    return h[a.y]>h[b.y];
}

int find(int x){
    if(fa[x]==x) return x;
    return fa[x]=find(fa[x]);
}

void kruskal(){
    sort(a+1,a+m+1,cmp);
    int k=0;
    for(int i=1;i<=m;i++){
        int dx=find(a[i].x),dy=find(a[i].y);
        if(dx!=dy){
            fa[dx]=dy;
            ans+=a[i].k;
            k++;
        }
        if(k==n-1) break;;
    }
    printf("%d %lld",k+1,ans);
}

int main(){
    read(n);read(m);
    for(int i=1;i<=n;i++) read(h[i]),fa[i]=i;
    for(int i=1;i<=m;i++){
        int x,y,z;
        read(x);read(y);read(z);
        if(h[x]<h[y]) swap(x,y);
        a[i]=(edge){x,y,z};
    }
    kruskal();
}
View Code

然后。。。。。

我们会发现是什么没考虑到呢?接着便可以发现可能有出发点达不到的地方,这些地方是没用的。

那么就可以考虑记录1可以利用的边,用这些边跑Kruskal。在最初读入时对于高度不同的点建有向边

高度相同建双向边,跑一边dfs,要判断是否遍历过

#include<bits/stdc++.h>
using namespace std;
#define ll long long

const int maxn=1000005;
int n,m,_n=1,cnt;
ll ans;
int fa[maxn],h[maxn];
bool vis[maxn];
vector<pair<int,ll> >e[maxn];
struct edge{
    int x,y;
    ll k;
}a[maxn];

template<class T>inline void read(T &x){
    x=0;char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
}

void swap(ll &x,ll &y){ll t=x;x=y;y=t;}

bool cmp(edge a,edge b){
    if(h[a.y]==h[b.y]) return a.k<b.k;
    return h[a.y]>h[b.y];
}

int find(int x){
    if(fa[x]==x) return x;
    return fa[x]=find(fa[x]);
}

void kruskal(){
    sort(a+1,a+cnt+1,cmp);
    int k=0;
    for(int i=1;i<=cnt;i++){
        int dx=find(a[i].x),dy=find(a[i].y);
        if(dx!=dy){
            fa[dx]=dy;
            ans+=a[i].k;
            k++;
        }
        if(k==_n-1) break;
    }
    printf("%d %lld",_n,ans);
}

void dfs(int u,int f){
    for(unsigned int i=0;i<e[u].size();i++){
        int v=e[u][i].first;
        if(v==f) continue;
        a[++cnt]=(edge){u,v,e[u][i].second};
        if(!vis[v]){
            vis[v]=true;_n++;
            dfs(v,u);
        }
    }
}

int main(){
    read(n);read(m);
    for(int i=1;i<=n;i++) read(h[i]),fa[i]=i;
    for(int i=1;i<=m;i++){
        int x,y,z;
        read(x);read(y);read(z);
        if(h[x]<h[y]) swap(x,y);
        e[x].push_back(make_pair(y,z));
        if(h[x]==h[y]) e[y].push_back(make_pair(x,z));
    }
    vis[1]=true;
    dfs(1,0);
    kruskal();
}
View Code

 

posted @ 2019-07-11 20:09  _JSQ  阅读(230)  评论(0编辑  收藏  举报