启发式合并学习笔记
启发式合并
upd:学了线段树合并之后回来,决定将两篇分开
关于启发式合并:
首先对于启发式算法的定义:
启发式算法是基于人类的经验和直观感觉,对一些算法的优化。
比如启发式搜索\(A^*\)算法。
至于启发式合并是什么,请先考虑一个问题:将\(n\)个元素个数为\(m\)的线性数据结构合并,求时间复杂度
如果用朴素算法合并,每次合并的最坏时间复杂度为\(O(m)\),\(n\)个元素合并的最坏时间复杂度为\(O(nm)\),属实是拉跨
考虑每次合并两个数据结构时,将个数少的合并到个数多的数据结构时的时间复杂度,即每次合并复杂度为\(O(\min(m_1,m_2))\)
似乎没啥软用?
如果注意到这样合并会产生一个性质:每次合并后个数为合并前少的部分的个数的两倍以上,所以每个元素最多被合并\(logn\)次(可以自己想想为啥),总时间复杂度也就变成\(O(nlogm)\)
同时,启发式合并也是很多高级算法的前置知识,比如线段树的合并,如果用启发式合并,可以将复杂度优化到\(mlog^2m\)(线段树合并小蒟蒻还不会,所以没写)
总而言之启发式合并核心思想就一句话:把小集合的合并到大的里。
模板题:
1、
洛谷 P3201 [HNOI2009] 梦幻布丁
链表+启发式合并
题意很容易理解,此处不在赘述。
其中的“将颜色\(x\)的布丁全部变成颜色\(y\)”操作可以抽象成一个合并过程。
因为数据范围是\(10^5\),虽然原始数据可以\(n^2\)过百万,但后来被卡了kk/
所以要考虑使用启发式合并。
对于题目中每一种颜色的元素,创造一个链表,每次染色操作就把标记为\(x\)的链合并到标记为\(y\)的链上(要判断一下,保证x是更短的链)。
注意一个细节:要开一个数组存储某个链表的当前颜色。
#include <bits/stdc++.h>
using namespace std;const int N=2e6;
int color[N],fir[N],nt[N],now[N],cnt[N],st[N],ans,n,m;
void merge(int x,int y){
cnt[y]+=cnt[x];cnt[x]=0;
for(int i=fir[x];i;i=nt[i]){if(color[i+1]==y) ans--;if(color[i-1]==y) ans--;}
for(int i=fir[x];i;i=nt[i])color[i]=y;
nt[st[x]]=fir[y];fir[y]=fir[x];fir[x]=st[x]=cnt[x]=0;
}int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>color[i];now[color[i]]=color[i];
if(color[i]^color[i-1])ans++;
if(!fir[color[i]]) st[color[i]]=i;
cnt[color[i]]++;nt[i]=fir[color[i]];fir[color[i]]=i;
}
for(int i=1,opt,x,y;i<=m;i++){
cin>>opt;if(opt==2) cout<<ans<<endl;
else{
cin>>x>>y;if(x==y) continue;
if(cnt[now[x]]>cnt[now[y]])swap(now[x],now[y]);
x=now[x],y=now[y];if(cnt[x])merge(x,y);
}
}return 0;
}
2、
洛谷 P5290 [十二省联考 2019] 春节十二响
配对堆+启发式合并
题意:有一个树,树上每个点带点权,现在将点划成若干个集合,要求每个集合里的点没有祖先后代关系,集合的权值是集合里点权最大的点的权值,求一个划分方案使所有集合权值总和最小。
做法:
- 由链的部分分可以发现,对于一条链可以直接对于左右各开一个堆,然后每次左右各弹一个取\(max\),我们思考怎么由链拓宽到树上。
- 以二叉树为例,它的每一个二叉可以看做一条链,左右两部分按照部分分的方式合并之后就变成了条新的链,链上每个点为之前两条“子链”的两个对应点的\(max\),然后递归的做下去就好了。
- 至于多叉树,显然和二叉树一样。
时间复杂度:
- 理论上的时间复杂度应为\(O(nlog^2n)\),但是这道题的启发式合并与传统的启发式合并有所不同,实际上的时间复杂度为\(O(nlogn)\)
- 因为在合并的过程中,并没有“合并”进去,而是把小的那部分“贴”上去后直接把小的“丢掉了”,每个点只会被遍历一次
- 每弹出一个点的时间复杂度为\(O(logn)\),所以总时间复杂度为\(O(nlogn)\)
最后,不开long long见祖宗,数组开小见祖宗。
#include <bits/stdc++.h>
#define int long long
using namespace std;const int N=5e5;
int n,a[N],fir[N],cnt,ans;stack<int> s;priority_queue<int> q[N];
struct edge{int v,nt;}e[N];void into(int u,int v){e[++cnt].v=v;e[cnt].nt=fir[u];fir[u]=cnt;}
void merge(int x,int y){
if(q[x].size()<q[y].size()) swap(q[x],q[y]);
while(!q[y].empty()){s.push(max(q[x].top(),q[y].top()));q[x].pop();q[y].pop();}
while(!s.empty())q[x].push(s.top()),s.pop();
}void dfs(int x){
for(int i=fir[x];i;i=e[i].nt)dfs(e[i].v),merge(x,e[i].v);
q[x].push(a[x]);
}signed main(){
cin>>n;for(int i=1;i<=n;i++) cin>>a[i];
for(int i=2,u;i<=n;i++)cin>>u,into(u,i);dfs(1);
while(!q[1].empty())ans+=q[1].top(),q[1].pop();
cout<<ans<<endl;return 0;
}