两种并查集
模板
int find(int x){
return par[x]==x?x:par[x]=find(par[x]);
}种类(扩展域)并查集
开多倍空间,维护种类间的关系
带权并查集
定义
可撤销并查集
cpp
struct DSU{
vector<int> par,siz;
stack<int> st;
int tot=0;
DSU(){}
DSU(int n){
par.resize(n);
siz.assign(n,1);
iota(par.begin(),par.end(),0);
}
int find(int x){
return par[x]==x?x:find(par[x]);
}
bool merge(int x,int y,int op){
x=find(x),y=find(y);
if(x==y)return false;
if(siz[x]<siz[y])swap(x,y);
par[y]=x;
siz[x]+=siz[y];
if(op){
st.push(y);
tot++;
}
return true;
}
void del(){
if(!tot)return;
tot--;
int y=st.top();
st.pop();
siz[par[y]]-=siz[y];
par[y]=y;
}
void back(int t=0){
while(tot>t)del();
}
int size(int x){
return siz[find(x)];
}
};CF891C
题意:
n个点m条边,q次询问,每次询问给多个点的编号,问是否能在同一颗最小生成树内
思路:
- 离线查询
- 对于每个询问的边排序,先按照边权排序后按照序号排序
- 如果当前的边与上一条处理的边边权不同(实际为将可撤销边变为不可撤销,避免误删)或者不在同一个查询(之前连上的可撤销边与当前边具有相同权值,删掉增加可能性),那么就可以把之前在同一个查询的边全部撤销了
- 然后将小于当前边连接,标记为不可撤销边
- 判断两点是否连通,如果是则连接上,加入可撤销边
cpp
struct DSU{
vector<int> par,siz;
stack<int> st;
int tot=0;
DSU(){}
DSU(int n){
par.resize(n);
siz.assign(n,1);
iota(par.begin(),par.end(),0);
}
int find(int x){
return par[x]==x?x:find(par[x]);
}
bool merge(int x,int y,int op){
x=find(x),y=find(y);
if(x==y)return false;
if(siz[x]<siz[y])swap(x,y);
par[y]=x;
siz[x]+=siz[y];
if(op){
st.push(y);
tot++;
}
return true;
}
void del(){
if(!tot)return;
tot--;
int y=st.top();
st.pop();
siz[par[y]]-=siz[y];
par[y]=y;
}
void back(int t=0){
while(tot>t)del();
}
};
void solve(){
int n,m,q;
cin>>n>>m;
vector<array<int,3>> edge(m+1),g(m+1);
for(int i=1;i<=m;i++){
auto &[x,y,z]=edge[i];
cin>>x>>y>>z;
}
cin>>q;
vector<array<int,2>> tmp;
vector<int> ans(q+1,1);
for(int i=1;i<=q;i++){
int k;
cin>>k;
while(k--){
int y;
cin>>y;
tmp.push_back({i,y});
}
}
g=edge;
sort(edge.begin()+1,edge.end(),[&](auto x,auto y){return x[2]<y[2];});
sort(tmp.begin(),tmp.end(),[&](auto x,auto y){
if(g[x[1]][2]==g[y[1]][2])return x[0]<y[0];
return g[x[1]][2]<g[y[1]][2];
});
DSU dsu(n+1);
for(int i=0,j=1;i<tmp.size();i++){
auto [pos,id]=tmp[i];
if(!ans[pos])continue;
if(i&&(tmp[i-1][0]!=tmp[i][0]||g[tmp[i-1][1]][2]!=g[tmp[i][1]][2]))dsu.back();
while(j<=m&&edge[j][2]<g[id][2]){
dsu.merge(edge[j][0],edge[j][1],0);
j++;
}
if(dsu.find(g[id][0])==dsu.find(g[id][1])){
ans[pos]=0;
}
else dsu.merge(g[id][0],g[id][1],1);
}
for(int i=1;i<=q;i++)cout<<(ans[i]?"YES\n":"NO\n");
}可撤销并查集与分治应用
cpp
void dfs(int l,int r){
if(l==r){
//update ans
return;
}
int mid=l+r>>1;
int ts=dsu.tot;
for(int i=l;i<=mid;i++)//add edge
dfs(mid+1,r);
dsu.back(ts);
for(int mid+1;i<=r;i++)//add edge
dfs(l,mid);
dsu.back(ts);
}CF1681F Unique Occurrences
题意:
给一棵树每棵树上有权值,求
思路:
- 可以发现对于一条边,它的贡献为断开与他相同颜色的边,两边连通块大小的乘积
- 把颜色看作一个点,可以用dfs做到连上除当前颜色的所有边