惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog RSS Feed
量子位
Y
Y Combinator Blog
大猫的无限游戏
大猫的无限游戏
B
Blog
U
Unit 42
C
Check Point Blog
I
InfoQ
aimingoo的专栏
aimingoo的专栏
雷峰网
雷峰网
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 【当耐特】
人人都是产品经理
人人都是产品经理
The Cloudflare Blog
H
Help Net Security
MongoDB | Blog
MongoDB | Blog
博客园 - Franky
H
Hackread – Cybersecurity News, Data Breaches, AI and More
J
Java Code Geeks
Microsoft Azure Blog
Microsoft Azure Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
云风的 BLOG
云风的 BLOG
宝玉的分享
宝玉的分享
爱范儿
爱范儿

Ariasakaの小窝

Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝 Ariasakaの小窝
Ariasakaの小窝
2023-07-29 · via Ariasakaの小窝

前言

前往以下网站,不保证安全性哦喵~P2573 【SCOI2012】 滑雪洛谷

调了一个下午终于弄出来了!!!

最近开始毛616的CG当封面偷懒了qwq

题目

查看题面

#### [SCOI2012] 滑雪 ##### 题目描述 a180285 非常喜欢滑雪。他来到一座雪山,这里分布着 $m$ 条供滑行的轨道和 $n$ 个轨道之间的交点(同时也是景点),而且每个景点都有一编号 $i\space (1 \le i \le n)$ 和一高度 $h_i$。 a180285 能从景点 $i$ 滑到景点 $j$ 当且仅当存在一条 $i$ 和 $j$ 之间的边,且 $i$ 的高度不小于 $j$。与其他滑雪爱好者不同,a180285 喜欢用最短的滑行路径去访问尽量多的景点。如果仅仅访问一条路径上的景点,他会觉得数量太少。 于是 a18028 5拿出了他随身携带的时间胶囊。这是一种很神奇的药物,吃下之后可以立即回到上个经过的景点(不用移动也不被认为是 a180285 滑行的距离)。 请注意,这种神奇的药物是可以连续食用的,即能够回到较长时间之前到过的景点(比如上上个经过的景点和上上上个经过的景点)。 现在,a180285站在 $1$ 号景点望着山下的目标,心潮澎湃。他十分想知道在不考虑时间胶囊消耗的情况下,以最短滑行距离滑到尽量多的景点的方案(即满足经过景点数最大的前提下使得滑行总距离最小)。你能帮他求出最短距离和景点数吗? ##### 输入格式 输入的第一行是两个整数 $n,m$。 接下来一行有 $n$ 个整数 $h_i$,分别表示每个景点的高度。 接下来 $m$ 行,表示各个景点之间轨道分布的情况。每行三个整数 $u,v,k$,表示编号为 $u$ 的景点和编号为 $v$ 的景点之间有一条长度为 $k$ 的轨道。 ##### 输出格式 输出一行,表示 a180285 最多能到达多少个景点,以及此时最短的滑行距离总和。 ##### 样例 #1 ###### 样例输入 #1 3 3 3 2 1 1 2 1 2 3 1 1 3 10 ###### 样例输出 #1 3 2 ##### 提示 【数据范围】 对于 $ 30% $ 的数据,$ 1 \le n \le 2000 $; 对于 $ 100% $ 的数据,$ 1 \le n \le 10^5 $。 对于所有的数据,保证 $ 1 \le m \le 10^6 $ , $ 1 \le h_i \le 10^9 $ ,$ 1 \le k_i \le 10^9 $。

题解

首先不难想到这是一个最小生成树题目:a180285 从点 1 出发,他希望遍历尽可能多的点且他可以回退,需要求此时代价最小路径的长度。

考虑使用 Kruskal 或 Prim 算法,因为 Kruskal 编码比较简单,遂采用该算法。

因为多个滑雪站之间只有高度较高的才能前往高度较低的站点,即对于任意 $G(V,E)$,当 $i,j\in V$ 时且 $h_i\ge h_j$ 时才存在 $(i,j)\in E$,所以我们需要根据 $h_u$ 和 $h_v$ 的关系来确定需要添加的边。

然后我们从起点 1 开始执行 BFS (DFS也可),算出从起点出发最多可以到达的顶点个数 $tot$ 。

为了方便,我开了一个前向星(纠正:因本蒟蒻之前的理解偏差,这并不叫做前向星,叫做存边数组差不多)和一个邻接表分别用于 Kruskal 和 BFS(都是打的 vector qwq)。

接下来排序并执行 Kruskal。排序需要注意一点:直接根据每一条边的权值 $w$ 排序的贪心策略是错误的,可以考虑这么一种情况:

这是一张图,其中每个节点上面的 $vx$ 表示编号,$h: x$ 表示高度,边上的数字表示权值。如果我们按照边权排序后选择的话,就会选到 $(4,3)=1$, $(1,3)=4$ 和 $(1,2)=4$,即上面描红了的边。然而这是无向图,我们就会发现 $v1$ 并不能到达 $v4$,然而我们要是选择了 $(1,4)=11$,虽然代价稍大,但是可以保证选到最多的点。

因为高度呈降序排列,前面的点一定可以到达后面的点,所以我们在高度上贪心取最小值即可保证尽可能选到多的点,当然高度一样时肯定也需要取权值较小的。

有几个坑:

坑 1:注意 BFS 时的 vis 数组,处理 Kruskal 时如果 !vis[u]||!vis[v] 时需要跳过(因为无法走到)。

坑 2:建边处理高度时需要同时考虑开的一个前向星和一个邻接表,不能少一个,否则必爆 0 ~ 18

坑 3:处理 Kruskal 时,如果 cnt==tot-1 即已遍历边数已经形成树形结构时就得 break 了。

那么代码就很简单了(但是注意有很多坑+没看注释导致的CE)

代码

偷懒,所以用了新特性,注意开 C++20以上。

注释版

CPP
#include <bits/stdc++.h>
#define int long long // 见祖宗+偷懒
using namespace std;
int n,m,fa[100005],tot=1,ans,vis[100005],h[100005];
struct node {int x,y,w,h;}; // 前向星,存起点终点和高度
vector<node> star; // 前向星
vector<int> lst[100005]; // 邻接表
queue<int> q; // BFS用
inline void add(int u,int v,int w){
    if(h[u]>=h[v]) // 根据高度情况考虑,注意两点高度相等时也可滑过
        star.push_back({u,v,w,h[v]}), // 前向星建图
        lst[u].push_back(v); // 邻接表建图
    if(h[u]<=h[v]) // 同上
        star.push_back({v,u,w,h[u]}),
        lst[v].push_back(u);
}
inline int find(int x){ // 三目表达式版简化find函数
    return x==fa[x]?x:fa[x]=find(fa[x]);
}
signed main(){
    ios::sync_with_stdio(0); // 关流同步,卡常(实际上题目给的5秒够用)
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>h[i],fa[i]=i; // 初始化
    for(int i=1,u,v,c;i<=m;i++)
        cin>>u>>v>>c,add(u,v,c); // 加边
    q.push(1),vis[1]=1; // BFS部分,起点入队
    while(!q.empty()){
        int u=q.front();q.pop();
        for(auto &it:lst[u])
            if(!vis[it]) // 计算可到达次数
                vis[it]=1,tot++,q.push(it);
    }
    sort(star.begin(),star.end(),[](node a,node b){
        return a.h==b.h?a.w<b.w:a.h>b.h;
    }); // 注意需要按高度排序,具体见TJ.
    // 这里用了C++11新特性lambda表达式,可以内联进参数里面
    for(int cnt=0;auto it:star){ // 遍历前向星跑 Kruskal
        // 这里的初始化cnt偷懒用了C++20新特性在范围循环初始化
        if(vis[it.x]&&vis[it.y]){ 
            int x=find(it.x),y=find(it.y); // 并查集板子
            if(x==y) continue;
            fa[x]=y,cnt++,ans+=it.w; // 算答案
            if(cnt==tot-1) break; // 边界条件,见TJ
        }
    }
    cout<<tot<<" "<<ans;
    return 0;
}

无注释版

C++
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n,m,fa[100005],tot=1,ans,vis[100005],h[100005];
struct node {int x,y,w,h;};
vector<node> star;
vector<int> lst[100005];
queue<int> q;
inline void add(int u,int v,int w){
    if(h[u]>=h[v])
        star.push_back({u,v,w,h[v]}),
        lst[u].push_back(v);
    if(h[u]<=h[v])
        star.push_back({v,u,w,h[u]}),
        lst[v].push_back(u);
}
inline int find(int x){
    return x==fa[x]?x:fa[x]=find(fa[x]);
}
signed main(){
    ios::sync_with_stdio(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>h[i],fa[i]=i;
    for(int i=1,u,v,c;i<=m;i++)
        cin>>u>>v>>c,add(u,v,c);
    q.push(1),vis[1]=1;
    while(!q.empty()){
        int u=q.front();q.pop();
        for(auto &it:lst[u])
            if(!vis[it])
                vis[it]=1,tot++,q.push(it);
    }
    sort(star.begin(),star.end(),[](node a,node b){
        return a.h==b.h?a.w<b.w:a.h>b.h;
    });
    for(int cnt=0;auto it:star){
        if(vis[it.x]&&vis[it.y]){
            int x=find(it.x),y=find(it.y);
            if(x==y) continue;
            fa[x]=y,cnt++,ans+=it.w;
            if(cnt==tot-1) break;
        }
    }
    cout<<tot<<" "<<ans;
    return 0;
}

emm 你谷这道题是蓝...

洛谷 P2573 [SCOI2012] 滑雪 题解