首页 > 其他分享 >【转载】启发式合并

【转载】启发式合并

时间:2024-08-29 16:39:39浏览次数:6  
标签:son cnt sub int 合并 id 启发式 转载 mx

https://zhuanlan.zhihu.com/p/560661911

数据结构学习笔记(8) 启发式合并

启发式合并是用来解决子树中的统计问题

在codeforces上叫做dsu on tree(树上启发式合并)。这里我们主要是来讲在树上进行启发式合并。

实际上之前我有讲过启发式合并严格鸽:启发式合并 看似暴力实则很快的算法

还有利用启发式合并的并查集严格鸽:ACM——可撤销并查集教程

但是没有讲过树上启发式合并。

我们一般需要维护一个 sub[u]sub[u] ,表示以 uu 为根的子树中的点。

下图中 sub[3]=[3,6,7,8,9]sub[3] = [3,6,7,8,9]

但是如果暴力维护每个 sub[u]sub[u] 肯定是会爆炸的。

但是


严格鸽:启发式合并 看似暴力实则很快的算法

启发式合并就是在合并的时候将size小的那个集合合并到size大的那个集合里面。

比如[1,2,3] 和 [3,5,6,7] 合并,选择遍历前者来把元素放入后者。

void merge(vector<int>& a, vector<int>& b) {
    if (a.size() > b.size()) {
        for (int x : b)a.push_back(x);
    }
    else {
        for (int x : a)b.push_back(x);
    }
}

初看上可能感觉这就是个暴力。但是我们分析一下每个元素被push_back()了多少次。

一个集合中的元素被放入另一个集合中会被push_back()一次。但是这个元素所在的集合的大小至少扩大了一倍。所以一个元素最多被push_back()O(log(N))O(log(N)) 次。


也就是用启发式合并,总的时间复杂度O(nlogn)\rm O(nlogn) 。

对于 sub[u]\rm sub[u] ,可以从其子节点 v\rm v ,利用启发式合并进行转移。

这里考虑下实现,一般的做法是,我们开一个

vector<int>sub[N]

mxson\rm mx_{son} 表示子树最大的子节点。

然后我们把其它的子节点中的 subsub 都放到这个 mxsonmx_{son} 中。

然后把这个 mxsonmx_{son} 复制给 uu ,但是因为有个复制操作,所以需要。

id[u]id[u] 表示 uu 被映射到了哪个位置。

这样有以下代码

vector<int>sub[N];
void dfs(int u, int fa) {
    id[u] = ++tot;
    int mx_son = -1, mx_sz = 0;
    for (int v : g[u]) {
        if (v == fa)continue;
        dfs(v, u);
        if (sub[id[v]].size() > mx_sz) {
            mx_sz = sub[id[v]].size();
            mx_son = v;
        }
    }
    if (mx_son != -1)id[u] = id[mx_son];//复制操作
    for (int v : g[u]) {
        if (v == fa)continue;
        if (v == mx_son)continue;
        for (int son : sub[id[v]])
            sub[id[u]].push_back(son);
    }
    sub[id[u]].push_back(u);
}

当然优化复制操作可以用c++11的move。

其实这样就可以直接做题了

题目链接:

Problem - E - Codeforces

题意:

题意来源洛谷


做法:

我们在记录子树出现了哪些节点之外,还需要记录每种颜色出现的次数。

所以我们直接套一个结构体

struct node {
    int mx_cnt = 0;//最多的出现次数
    ll mx_sum = 0;//出现次数最多的颜色的编号和
    map<int, int>cnt;
    vector<int>list;
    void add(int u) {
        cnt[c[u]]++;
        if (cnt[c[u]] > mx_cnt)mx_cnt = cnt[c[u]], mx_sum = c[u];
        else if (cnt[c[u]] == mx_cnt)mx_sum += c[u];
        list.push_back(u);
    }
    int size() { return list.size(); }
}sub[N];

这里我们选择直接套一个map,复杂度为 O(nlog2n)\rm O(nlog^2n) , 10510^5 的数据完全够用。

这样我们套一下上面的代码,就可以愉快的做出本题了。

code

const int N = 1e5 + 5;
int n, c[N], id[N], tot = 0;
struct node {
    int mx_cnt = 0;//最多的出现次数
    ll mx_sum = 0;//出现次数最多的颜色的编号和
    map<int, int>cnt;
    vector<int>list;
    void add(int u) {
        cnt[c[u]]++;
        if (cnt[c[u]] > mx_cnt)mx_cnt = cnt[c[u]], mx_sum = c[u];
        else if (cnt[c[u]] == mx_cnt)mx_sum += c[u];
        list.push_back(u);
    }
    int size() { return list.size(); }
}sub[N];
ll ans[N];
vector<int>g[N];
void dfs(int u, int fa) {
    id[u] = ++tot;
    int mx_son = -1, mx_sz = 0;
    for (int v : g[u]) {
        if (v == fa)continue;
        dfs(v, u);
        if (sub[id[v]].size() > mx_sz) {
            mx_sz = sub[id[v]].size();
            mx_son = v;
        }
    }
    if(mx_son!=-1)id[u] = id[mx_son];
    for (int v : g[u]) {
        if (v == fa)continue;
        if (v == mx_son)continue;
        for (int son : sub[id[v]].list)
            sub[id[u]].add(son);
    }
    sub[id[u]].add(u);
    ans[u] = sub[id[u]].mx_sum;
}
void slove() {
    cin >> n;
    for (int i = 1; i <= n; i++)cin >> c[i];
    for (int i = 1; i <= n - 1; i++) {
        int u, v; cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(1, 0);
    for (int i = 1; i <= n; i++)cout << ans[i] << " ";
    cout << endl;
}

不过这个是一个比较裸的启发式合并的题目,大家可以做做下面两道题目练习。

严格鸽:Codeforces Round #760 (Div. 3) G(离线/并查集/数据结构)

严格鸽:Educational Codeforces Round 132 C(贪心) D E(启发式合并 + 懒标记)



除了启发式合并,我们还可以用线段树合并来解决此类问题。

标签:son,cnt,sub,int,合并,id,启发式,转载,mx
From: https://www.cnblogs.com/gutongxing/p/18386984

相关文章

  • 【Markdown笔记】设置字体颜色——转载https://blog.csdn.net/u012028275/article/det
     【Markdown笔记】设置字体颜色dadalaohua于2021-04-0517:53:19发布阅读量5.7w 收藏 293点赞数103分类专栏: Markdown笔记 文章标签: markdown latex html版权GitCode开源社区文章已被社区收录加入社区Markdown笔记专......
  • 使用open3d合并ply模型
    importopen3daso3dfromscipy.ndimageimportbinary_fill_holesdefmerge_ply(ply1,ply2,output_path):#加载两个多边形模型mesh1=o3d.io.read_triangle_mesh(ply1)mesh2=o3d.io.read_triangle_mesh(ply2)#使用+运算符合并两个多边形模型......
  • element中表格合并单元格
    最近要写一个如下图的项目,需要合并单元格 <el-table:data="list"borderstyle="width:1000px;":span-method="objectSpanMethod"><el-table-columnalign="center"prop="frist_name":label="list[0].specName&q......
  • 56. 合并区间【 力扣(LeetCode) 】
    一、题目描述  以数组intervals表示若干个区间的集合,其中单个区间为intervals[i]=[starti,endi]。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。二、测试用例示例1:输入:intervals=[[1,3],[2,6],[8,10],[15,18]]输......
  • 视频合并怎么操作?三个技巧让你的视频合并无瑕疵
    视频制作和剪辑已经成为一种普遍现象,无论是业余爱好者还是专业团队,都有将多个视频片段合并为一个完整视频的需求。而这种合并不仅能够提升视频的流畅度,还能极大地丰富观众的观看感受。下面将向大家介绍3种视频合并工具在线使用方法。对于那些还在寻找如何将两个视频合并为一......
  • 合并两个排序的数组
     输入:nums1=[1,2,3,0,0,0],m=3nums2=[2,5,6],n=3输出:[1,2,2,3,5,6] publicvoidmergeArray(int[]nums1,intm,int[]nums2,intn){inti=m-1;intj=n-1;intindex=m+n-1;while(index>=0){......
  • python 多张图片合并
    有一堆雷达图,想放到一张图上展示#!usr/bin/envpython#-*-coding:utf-8-*-"""@author:Suyue@file:piccon.py@time:2024/08/27@desc:"""importosimportPIL.ImageasImageIMAGES_PATH=r'F:/picture//'#图片集地址IMAGES_FORM......
  • oracle system信息统计,​Oracle的SYSTEM和SYSAUX表空间 转载:https://blog.csdn.net
    一般情况下,业务数据应该存放在单独的数据表空间,而不应该使用系统已存在的表空间,尤其不能将业务数据保存到SYSTEM和SYSAUX表空间中,所以,DBA需要着重关注SYSTEM和SYSAUX表空间的占用情况。Oracle服务器使用SYSTEM表空间管理整个数据库。这个表空间包含系统的数据字典和关于数据库的......
  • OceanBase-合并问题-工单处理常用SQL1
     OceanBase-合并问题-工单处理常用SQL1全量合并-------------------------------------------------------------OB默认是增量合并【发起的指定表的全量合并】1、修改表的模式,全量合并https://www.oceanbase.com/knowledge-base/oceanbase-database-2000000102......
  • 高效数据整合:多个Excel表格的汇总与合并
    Excel文件很多都是应用在数据操作中,总是少不了需要将几份excel文件中的数据进行合并来进行应用。今天给大家分享两种excel工作表数据合并的方法。方法一:复制粘贴如果是少量的数据需要合并到一起,我们可以直接将数据复制过来复制成功之后,在工作表确定想要防止数据的位置,点击......