题意
如题,一开始有 \(n\) 个小根堆,每个堆包含且仅包含一个数。接下来需要支持两种操作:
-
1 x y
:将第 \(x\) 个数和第 \(y\) 个数所在的小根堆合并(若第 \(x\) 或第 \(y\) 个数已经被删除或第 \(x\) 和第 \(y\) 个数在用一个堆内,则无视此操作)。 -
2 x
:输出第 \(x\) 个数所在的堆最小数,并将这个最小数删除(若有多个最小数,优先删除先输入的;若第 \(x\) 个数已经被删除,则输出 \(-1\) 并无视删除操作)。
对于 \(100\%\) 的数据:\(n\le 10^5\),\(m\le 10^5\),初始时小根堆中的所有数都在 int
范围内。
思路
【模板】左偏树?【模板】启发式合并!
左偏树这种不常用的 useless algo 想来想去也没啥实战意义,真要说的话也完全可以用动态开点权值线段树的线段树合并来上位替代。
于是我们就有了充分的理由用邪道来艹题。
由于只有合并莫得分裂,考虑启发式合并。每次将较小的堆向较大的堆合并即可做到均摊复杂度 \(O(n\log n)\)。使用并查集维护所有点的所属堆。
对于删除直接弹堆顶然后打标记即可。
代码
“ 主播,set
是你爹?用小根堆压掉取堆顶的一个 \(\log\) 会死?”
const ll maxn=1e5+5;
ll fa[maxn];
bool del[maxn];
ll find(ll x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void merge(ll x,ll y){
ll u=find(x),v=find(y);
fa[u]=v;
}
struct node{
ll val,id;
friend bool operator < (const node &x,const node &y){
return x.val==y.val?x.id<y.id:x.val<y.val;
}
};
set<node>s[maxn];
ll n,m;
void solve(){
n=R,m=R;
for(ll i=1;i<=n;i++){
fa[i]=i;
ll x=R;
s[i].insert((node){x,i});
}
while(m--){
ll opt=R,x=R;
if(opt==1){
ll y=R;
if(del[x]||del[y]||find(x)==find(y)){
continue;
}
ll u=find(x),v=find(y);
if(s[u].size()<s[v].size())swap(u,v);
while(!s[v].empty()){
auto it=s[v].begin();
s[u].insert(*it);
s[v].erase(*it);
}
merge(v,u);
}
else {
if(del[x]){
puts("-1");
continue;
}
ll u=find(x);
auto it=s[u].begin();
//if(s[u].empty())cerr<<"zmhsn?"<<endl;
we(it->val);
del[it->id]=1;
s[u].erase(*it);
}
}
return ;
}
标签:ll,个数,fa,maxn,LuoguP3377,find,模板,左偏
From: https://www.cnblogs.com/rnfmabj/p/luogup3377.html