梦幻布丁
假设现在有
现在有一种合并操作,可以合并两个集合,假设将集合
假设我们依次合并(i.e.先将
但是我们现在采取一个优化,每次合并都将元素个数少的集合合并到元素个数多的集合里面,这样的话时间复杂度就会变为
证明:考虑每个元素对时间复杂度的贡献,为
然后来考虑这道题目。注意,我们的启发式合并每次是合并一个元素的,所以我们应该考虑假设我们每次只改变一个元素的颜色(而不是所有这个元素的颜色一起改变)答案会发生什么变化
比较显然,设
于是我们现在就可以找到一个
由于启发式合并会将更小的集合合并到更大的集合,涉及到集合的快速交换,所以我们需要一个散列表h[i]
来表示某一种颜色的位置,并且再用一个单独的数组p[i]
来表示这个颜色对应的散列表指针(这样在交换集合的时候就可以p
了)。注意h[i]
并不是表示颜色p[i]
才表示颜色p[i]
指向的散列表表示p[i]
不一定指向h[i]
),然后剩下的可以看y总的代码;注意其代码的color
数组并不是表示真实的颜色,color[i]!=color[j]
只能说明位置color[i]
,color[j]
,所以交换的操作显然正确,而且交换之后ans
显然不变;还要注意merge
函数的参数表里面是引用
但是有一种用vector
的更简单的写法。设vector<int> g[N]
,其中g[i]
表示颜色color
的意义与y总代码相同;然后利用vecotr
的成员函数swap
进行高效交换(这个时间复杂度是
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e6+10;
const ll mod=1e9+7;
int n,m;
int cnt,color[N];
vector<int> g[N];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&color[i]);
g[color[i]].push_back(i);
cnt+=(color[i]!=color[i-1]);
}
while(m--)
{
int op;
scanf("%d",&op);
if(op==1)
{
int x,y;
scanf("%d%d",&x,&y);
if(x==y) continue;
if(g[x].size()>g[y].size()) g[x].swap(g[y]);
//假设颜色x的数量更多,就要交换
//但是题目要求的是将x变成y不是将y变成x
//所以此时认为,原来是x的位置现在全部变成y,原来是y的位置现在全部变成x
//易知此时cnt不变
//然后再将此时颜色x全部变成颜色y即可
int v=color[g[y].back()];
for(int i=0;i<g[x].size();i++)
cnt-=(color[g[x][i]-1]==v)+(color[g[x][i]+1]==v);
while(g[x].size())
{
color[g[x].back()] = v;
g[y].push_back(g[x].back());
g[x].pop_back();
}
}
else printf("%d\n",cnt);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构