题解:Atcoder Beginner Contest abc469 F - GCD Maximum Spanning Tree

A~D题解

E题题解

题目描述

给你一个长度为 N 的正整数序列 A 。

有一个加权完整无向图,其顶点为 N 。

顶点编号为 1,2,\\dots,N 。

对于满足 1 \\leq i \\lt j \\leq N 的 i 和 j ,连接顶点 i 和 j 的边的权重是 A_i 和 A_j 的最大公约数。

求该图生成树中各条边的权重之和的最大可能值。

数据范围

  • 2 \\leq N \\leq 2 \\times 10\^5
  • 1 \\leq A_1 \\lt A_2 \\lt \\dots \\lt A_N \\leq 10\^6
  • 所有输入值均为整数。

输入样例1

复制代码
3
4 6 12

输出样例1

复制代码
10

样例1解释

完整的图形有以下三条边。

  • 连接顶点 1 和 2 的边,权重为 2 。
  • 连接顶点 1 和 3 的边,权重为 4 。
  • 连接顶点 2 和 3 的边,权重为 6 。

选择第二条和第三条边,权重总和为 10 ,这是可能的最大值。

解题思路

1. 最大生成树 Kruskal 思想

Kruskal 求最大生成树策略:从大到小依次选取边,若两个点不在同一连通块就选用这条边,累加权值。

常规做法:把所有边按权从大到小排序依次尝试合并。 本题边权是 \(\gcd\),无法枚举全部边,因此变换枚举对象 :不枚举点对,直接从大到小枚举公约数 d。

  • 我们认为:存在一条权值为 d 的边,连接任意两个是 d 的倍数的数字对应的顶点;
  • 从大到小遍历 d,优先使用权值更大的 d 合并连通块,完美契合最大生成树贪心。

2. 如何快速找到所有是 d 倍数的数

题目保证所有 \(A_i\) 互不相同。开设数组 \(\boldsymbol{pos\[\]}\): pos \[v\] = 值为 v 的顶点编号;不存在则为 0。

对于当前枚举的 d,遍历所有倍数 \(k=d,2d,3d\dots \le 10^6\): 若 \(posk\neq0\),说明存在顶点对应数值 k,收集这个顶点。

3. 连通块合并(并查集 DSU)

用 pre 保存上一个找到的属于 d 倍数的顶点。 不断取出新顶点 cur,尝试用权值 d 的边合并 pre 与 cur:

  • 合并成功:答案累加 d;
  • 合并失败:两点已经连通,舍弃这条边。

原理:同一批 d 的倍数点之间,只要依次两两相连,就能用权值 d 尽可能连通所有点,等价于在这批点之间添加若干权值 d 的边。

算法整体流程

  1. 输入序列,用pos数组记录每个数值对应的顶点编号,初始化并查集;
  2. 从大到小枚举公约数d(视作边权),优先尝试大权值的边;
  3. 遍历所有d的倍数,收集存在的顶点,依次尝试用权值d合并相邻顶点;
  4. 每次成功合并连通块,将d累加入答案;
  5. 枚举结束后输出总和,即为最大生成树权值和。

完整代码

复制代码
#include<bits/stdc++.h>
#define fr1(i,a,b) for(int (i)=(a);(i)<=(b);++(i))
#define fr2(i,a,b) for(int (i)=(a);(i)>=(b);--(i))
#define fv(i,p) for(auto (i):(p))
#define ll long long
#define ull unsigned ll
#define pii pair<int,int>
#define pll pair<ll,ll>
#define _1st first
#define _2nd second
#define y1 yy1
#define elif else if
#define debug cout<<endl<<"-------------------------------------------------------------"<<endl
using namespace std;
const int MAXN=2e5+5;
const int MAXA=1e6;
int pos[MAXA+5],fa[MAXN],a[MAXN],n;
int find(int x){
    if(fa[x]!=x)fa[x]=find(fa[x]);
    return fa[x];
}
bool unite(int x,int y){
    x=find(x),y=find(y);
    if(x==y)return 0;
    fa[y]=x;
    return 1;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(NULL);cout.tie(NULL);
    cin>>n;
    fr1(i,1,n){
        cin>>a[i];
        pos[a[i]]=i;
        fa[i]=i;
    }
    ll ans=0;
    fr2(d,MAXA,1){
        int pre=0;
        for(int k=d;k<=MAXA;k+=d){
            if(pos[k]==0)continue;
            int cur=pos[k];
            if(pre==0){
                pre=cur;
            }else{
                if(unite(pre,cur)){
                    ans+=d;
                }
                pre=cur;
            }
        }
    }
    cout<<ans;
    return 0;
}
相关推荐
小七在进步9 小时前
string的使用(1)
c++·visual studio
白杨尚青10 小时前
C++进阶篇(六):map 和 set 的模拟实现——用红黑树封装 mymap/myset(迭代器 + operator[])
开发语言·c++·迭代器·stl·set·map·红黑树
kloc13 小时前
聊天室项目入口网关子服务的实现
linux·c++
csdn_aspnet16 小时前
C++ 3个有序点的方向(Orientation of 3 ordered points)
c++·算法
小七在进步17 小时前
string的使用(2)
c++
青少儿编程课堂18 小时前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:折半搜索优化子集枚举(Meet in the Middle)解析
c++·python·算法·bfs·信息学竞赛
wo_jack19 小时前
C++ 入门笔记(十七):list 入门 —— 为什么 list 不能 sort?
数据结构·c++·面试·stl·list
青少儿编程课堂20 小时前
蓝桥杯青少组图形化 + 全国青少年信息素养大赛 + 电子学会Scratch考级:校园连连看解析
c++·python·算法·bfs·信息学竞赛
草莓熊Lotso21 小时前
【C++ 基于微服务的即时通讯系统】文件存储子服务实战:UUID生成、文件IO封装与全接口单元测试
开发语言·网络·数据库·c++·微服务·架构·单元测试
charliejohn1 天前
计算机考研 408 数据结构 图论-关键路径
数据结构·图论