启发式合并
启发式算法是什么?
启发式算法是基于人类的经验和直观感觉,对一些算法的优化。
比如说启发式搜索A*算法。
启发式合并是什么?
考虑一个问题:把n个总元素个数为m的数据结构合并起来(假设是线性的)。
每次合并复杂度最坏O(m),总复杂度O(nm)?显然无法接受。
每次把个数少的合并到个数多的?复杂度O(min(m1,m2))
好像没啥用?
可是我们注意到,每次合并后个数为合并前少的部分的个数的两倍以上,每个元素最多合并logm次,总复杂度O(mlogm)。
我们也可以启发式合并更加高级的数据结构,如heap,set,splay等,复杂度O(mlog^2m)
很玄学?但这个复杂度分析是对的,而且跑的也快。
题意就不赘述了。
对于每一个颜色,建一条链表。然后染色就是把链短的合并到链长的。
需要注意细节,如果把2染成3,但2的链比3的长,就需要把3的合并到2上。但是现在本应属于3的链在2上,就需要记一个该颜色的链现在在哪个颜色上,即是代码中的now数组。
#include<cstdio>
#define rep(i, a, b) for (register int i=(a); i<=(b); ++i)
#define per(i, a, b) for (register int i=(a); i>=(b); --i)
using namespace std;
void swap(int &x, int &y){x^=y^=x^=y;}
const int N=1000005;
int head[N], nxt[N], col[N], now[N], cnt[N], st[N], ans;
inline int read()
{
int x=0,f=1;char ch=getchar();
for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
return x*f;
}
void merge(int x, int y)
{
cnt[y]+=cnt[x]; cnt[x]=0;
for (int i=head[x]; i; i=nxt[i])
{
if (col[i+1]==y) ans--;
if (col[i-1]==y) ans--;
}
for (int i=head[x]; i; i=nxt[i]) col[i]=y;
nxt[st[x]]=head[y]; head[y]=head[x];
head[x]=st[x]=cnt[x]=0;
}
int main()
{
int n=read(), m=read();
rep(i, 1, n)
{
col[i]=read(); now[col[i]]=col[i];
if (col[i]^col[i-1]) ans++;
if (!head[col[i]]) st[col[i]]=i;
cnt[col[i]]++; nxt[i]=head[col[i]]; head[col[i]]=i;
}
rep(i, 1, m)
{
int opt=read();
if (opt==2) printf("%d\n", ans);
else
{
int x=read(), y=read();
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;
}
在看一道新鲜出炉的联考题:春节十二响
题意比较复杂,自己看吧QAQ 。
考虑链的部分分做法,将两条支链分别排序,然后从大到小加上两边的max即可。
那么我们就有了一个暴力做法。对每个点维护一个堆,每次像链那样暴力合并即可,复杂度大概是O(n^2logn)?
改成启发式合并就可以了,每次把小的堆合并到大的堆上。时间复杂度O(nlog^2n),其实跑的很快。
#include<cstdio>
#include<vector>
#include<queue>
#define rep(i, a, b) for (register int i=(a); i<=(b); ++i)
#define per(i, a, b) for (register int i=(a); i>=(b); --i)
using namespace std;
const int N=200005;
vector<int> G[N], Q;
priority_queue<int> q[N];
int val[N], id[N], n; long long ans;
inline int read()
{
int x=0,f=1;char ch=getchar();
for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
return x*f;
}
void dfs(int u)
{
for (int v: G[u])
{
dfs(v);
if (q[id[u]].size()<q[id[v]].size()) swap(id[u], id[v]);
int s=q[id[v]].size(); Q.clear();
rep(i, 1, s)
{
Q.push_back(max(q[id[u]].top(), q[id[v]].top()));
q[id[u]].pop(); q[id[v]].pop();
}
for (int i:Q) q[id[u]].push(i);
}
q[id[u]].push(val[u]);
}
int main()
{
n=read();
rep(i, 1, n) val[i]=read(), id[i]=i;
rep(i, 2, n) G[read()].push_back(i);
dfs(1);
while (!q[id[1]].empty()) ans+=q[id[1]].top(), q[id[1]].pop();
printf("%lld\n", ans);
return 0;
}
本来这里还有一个题,经提醒并不是启发式合并,故删去
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:使用Catalyst进行自然语言处理
· 分享一个我遇到过的“量子力学”级别的BUG。
· Linux系列:如何调试 malloc 的底层源码
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 物流快递公司核心技术能力-海量大数据处理技术
· 几个技巧,教你去除文章的 AI 味!
· 系统高可用的 10 条军规
· 如何在 Github 上获得 1000 star?
· 关于普通程序员该如何参与AI学习的三个建议以及自己的实践