可持久化并查集
P3402 可持久化并查集
链接:https://www.luogu.com.cn/problem/P3402
题意:给定 n 个集合,第 i 个集合内初始状态下只有一个数,为 i。有 m 次操作。操作分为 3 种:
-
1 a b 合并 a,b 所在集合
-
2 k 回到第 k 次操作(执行三种操作中的任意一种都记为一次操作)之后的状态
-
3 a b 询问 a,b 是否属于同一集合,如果是则输出 1 ,否则输出 0
思路:可持久化并查集
#include <bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define se second
#define ll long long
using namespace std;
const int maxn=2e5+5;
int n,m;
int root[maxn],ls[maxn*20],rs[maxn*20],sz[maxn*20],fa[maxn*20],no;
int build(int L,int R)
{
int rt=++no;
if(L==R)
{
fa[rt]=L;
sz[rt]=1;
return rt;
}
int mid=(L+R)>>1;
ls[rt]=build(L,mid);
rs[rt]=build(mid+1,R);
return rt;
}
int update(int pre,int p,int L,int R,int f,int s)
{
int rt=++no;
ls[rt]=ls[pre];
rs[rt]=rs[pre];
if(L==R)
{
fa[rt]=f;
sz[rt]=s;
return rt;
}
int mid=(L+R)>>1;
if(p<=mid) ls[rt]=update(ls[pre],p,L,mid,f,s);
if(p>mid) rs[rt]=update(rs[pre],p,mid+1,R,f,s);
return rt;
}
pii query(int rt,int p,int L,int R)
{
if(L==R) return {fa[rt],sz[rt]};
int mid=(L+R)>>1;
if(p<=mid) return query(ls[rt],p,L,mid);
if(p>mid) return query(rs[rt],p,mid+1,R);
}
pii find(int rt,int x)
{
pii res=query(rt,x,1,n);
if(res.fi==x) return res;
return find(rt,res.fi);
}
int main()
{
scanf("%d%d",&n,&m);
root[0]=build(1,n);
int op,u,v,k;
for(int i=1; i<=m; ++i)
{
scanf("%d",&op);
if(op==1)
{
scanf("%d%d",&u,&v);
root[i]=root[i-1];
pii ru=find(root[i],u);
pii rv=find(root[i],v);
if(ru.fi==rv.fi) continue;
if(ru.se>rv.se) swap(ru,rv);
root[i]=update(root[i],ru.fi,1,n,rv.fi,0);
root[i]=update(root[i],rv.fi,1,n,rv.fi,ru.se+rv.se);
}
else if(op==2)
{
scanf("%d",&k);
root[i]=root[k];
}
else
{
scanf("%d%d",&u,&v);
root[i]=root[i-1];
printf("%d\n",find(root[i],u)==find(root[i],v));
}
}
return 0;
}
P4768 [NOI2018]归程 (可持久化并查集)
链接:https://www.luogu.com.cn/problem/P4768
题意:给定 n 个点 m 条边的连通图,每条边带有高度 a ,长度 w 。每天的降水量为 h ,当边的高度不超过 h 时,会被淹没。如果边未被淹没可以开车前往,如果被淹没了就只能步行前往。现在 Yazid 需要从 v 节点回到 1 节点,他一开始是开着车的,下车步行之后,就不能再开车。给定 q 个询问,每个询问为 v 、q ,表示出发点 v 和今天的降水量,问从 v 到 1 的最短步行距离。 ( 1 ≤ n ≤ 2 × 1 0 5 , 1 ≤ m ≤ 4 × 1 0 5 ) (1\le n \le 2\times 10^5 ,1\le m \le 4\times 10^5) (1≤n≤2×105,1≤m≤4×105),强制在线
思路
#include <bits/stdc++.h>
#define fi first
#define se second
#define ll long long
using namespace std;
const int maxn=2e5+5,maxm=4e5+5,inf=2e9;
int t,n,m;
struct Edge
{
int u,v,w,a;
bool operator<(const Edge& b)const
{
return a<b.a;
}
};
struct DSU
{
int fa,sz,dis;
} dsu[maxm*20];
vector<pair<int,int> > e[maxn];
vector<int> b;
vector<Edge> edges;
int dis[maxn],visit[maxn];
void dijstra()
{
for(int i=0; i<=n; ++i) dis[i]=inf,visit[i]=0;
dis[1]=0;
priority_queue<pair<int,int> > pq;
pq.push({0,1});
while(!pq.empty())
{
int u=pq.top().se;
pq.pop();
if(visit[u]) continue;
visit[u]=1;
for(auto x: e[u])
{
int v=x.fi,w=x.se;
if(dis[u]+w<dis[v])
{
dis[v]=dis[u]+w;
pq.push({-dis[v],v});
}
}
}
}
int root[maxn],ls[maxm*20],rs[maxm*20],no;
int build(int L,int R)
{
int rt=++no;
if(L==R)
{
dsu[rt]= {L,1,dis[L]};
return rt;
}
int mid=(L+R)>>1;
ls[rt]=build(L,mid);
rs[rt]=build(mid+1,R);
return rt;
}
int update(int pre,int p,int L,int R,DSU val)
{
int rt=++no;
ls[rt]=ls[pre];
rs[rt]=rs[pre];
if(L==R)
{
dsu[rt]=val;
return rt;
}
int mid=(L+R)>>1;
if(p<=mid) ls[rt]=update(ls[pre],p,L,mid,val);
if(p>mid) rs[rt]=update(rs[pre],p,mid+1,R,val);
return rt;
}
DSU query(int rt,int p,int L,int R)
{
if(L==R) return dsu[rt];
int mid=(L+R)>>1;
if(p<=mid) return query(ls[rt],p,L,mid);
if(p>mid) return query(rs[rt],p,mid+1,R);
}
DSU find(int rt,int x)
{
DSU res=query(rt,x,1,n);
if(res.fa==x) return res;
return find(rt,res.fa);
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
edges.clear();
b.clear();
for(int i=1; i<=n; ++i) e[i].clear();
no=0;
for(int i=1; i<=m; ++i)
{
int u,v,w,a;
scanf("%d%d%d%d",&u,&v,&w,&a);
e[u].push_back({v,w});
e[v].push_back({u,w});
b.push_back(a);
edges.push_back({u,v,w,a});
}
dijstra();
sort(b.begin(),b.end());
sort(edges.begin(),edges.end());
root[m+1]=build(1,n);
for(int i=m; i>=1; --i)
{
root[i]=root[i+1];
int u=edges[i-1].u,v=edges[i-1].v;
DSU ru=find(root[i],u);
DSU rv=find(root[i],v);
if(ru.fa==rv.fa) continue;
if(ru.sz>rv.sz) swap(ru,rv);
root[i]=update(root[i],ru.fa,1,n, {rv.fa,0,0});
root[i]=update(root[i],rv.fa,1,n, {rv.fa,rv.sz+ru.sz,min(ru.dis,rv.dis)});
}
int q,k,s,p,v,ans=0;
scanf("%d%d%d",&q,&k,&s);
while(q--)
{
scanf("%d%d",&v,&p);
v=(v+k*ans-1)%n+1;
p=(p+k*ans)%(s+1);
int pos=upper_bound(b.begin(),b.end(),p)-b.begin()+1;
DSU res=find(root[pos],v);
ans=res.dis;
printf("%d\n",res.dis);
}
}
return 0;
}
zcmu1435. 盟国 (带删并查集)
链接:https://acm.zcmu.edu.cn/JudgeOnline/problem.php?id=1435
题意:给定 n 个国家 m 个操作。两种操作: M i j ,将 i j 变为同盟。S i ,将第 i 个国家从联盟中删去,问最后有多少个联盟
思路: 用 id 表示 u 真正的父节点
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,maxm=2e6+5;
int n,m;
int id[maxm],pa[maxm],sz[maxm],no;
int visit[maxm];
int find(int x)
{
return x==pa[x]?x:pa[x]=find(pa[x]);
}
void merge(int u,int v)
{
int ru=find(u),rv=find(v);
if(ru==rv) return;
pa[ru]=rv;
}
void del(int u)
{
id[u]=++no;
pa[no]=no;
}
int main()
{
int Case=0;
while(scanf("%d%d",&n,&m)&&(n||m))
{
memset(visit,0,sizeof(visit));
for(int i=1; i<=n; ++i) id[i]=pa[i]=i,sz[i]=1;
no=n;
char op[5];
int u,v;
while(m--)
{
scanf("%s%d",op,&u);
u++;
if(op[0]=='M')
{
scanf("%d",&v);
v++;
merge(id[u],id[v]);
}
else del(u);
}
int ans=0;
for(int i=1; i<=n; ++i)
{
int ru=find(id[i]);
if(visit[ru]) continue;
visit[ru]=1;
ans++;
}
printf("Case #%d: %d\n",++Case,ans);
}
return 0;
}
UVA-11987 Almost Union-Find (带删并查集)
链接:https://vjudge.net/problem/UVA-11987
题意:给定 n 个数,一开始每个数都在一个集合内。维护 m 个操作,有 3 种类型:
- 1 p q :将 p 所在集合与 q 所在集合合并
- 2 p q :将 p 合并到 q 所在集合
- 3 p : 查询 p 所在集合的元素个数,以及所有元素的和
( 1 ≤ n , m ≤ 1 0 5 ) (1\le n,m\le 10^5) (1≤n,m≤105)
思路:对于操作二,需要用到删除操作。删除以后再将 p 合并到 q 的集合上
- 用 id 数组表示元素真正的根所在的位置
- 大概就是开 n + m 长度的数组,删点时,先将自己的贡献在原集合中删去,然后再新开一个点用来合并
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
int n,m;
int pa[maxn<<1],id[maxn<<1],sz[maxn<<1],no;
ll sum[maxn<<1];
int find(int x)
{
return x==pa[x]?x:pa[x]=find(pa[x]);
}
void merge(int u,int v)
{
int ru=find(u),rv=find(v);
if(ru!=rv)
{
pa[ru]=rv;
sz[rv]+=sz[ru];
sum[rv]+=sum[ru];
}
}
void move(int u,int v)
{
int ru=find(id[u]);
int rv=find(id[v]);
if(ru==rv) return;
sz[ru]--;
sum[ru]-=u;
id[u]=++no;
pa[no]=no;
sum[no]=u;
sz[no]=1;
merge(id[u],id[v]);
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
for(int i=1; i<=max(no,n); ++i)
sum[i]=pa[i]=id[i]=i,sz[i]=1;
no=n;
while(m--)
{
int op,p,q;
scanf("%d",&op);
if(op==1)
{
scanf("%d%d",&p,&q);
merge(id[p],id[q]);
}
else if(op==2)
{
scanf("%d%d",&p,&q);
move(p,q);
}
else if(op==3)
{
scanf("%d",&p);
int ru=find(id[p]);
printf("%d %lld\n",sz[ru],sum[ru]);
}
}
}
return 0;
}
CF891C. Envy (贪心 + 并查集 || 可撤销并查集)
链接:https://codeforces.com/contest/891/problem/C
题意:给定 n 个点 m 条边的连通图。q 次询问,每次询问一个边集,问是否存在一个最小生成树包含这些边集
思路一:
- 首先根据边权对原图重构,最小的边权贪心的来说是必选的,对边权相同的进行连边。后面就相当于连通块对连通块连边,就会发生重边,这些重边就是不可能同时选择的,也就不可能出现在同一个边集中。
- 图重构完之后,就判断有没有形成环,形成了就不能再同一个边集中了。
思路二:
- 将同一个询问的多条边按权值分离,每次询问前将图中小于当前权值的边都通过并查集做一下联通,然后在判断当前询问的多条边能不能构成MST。查询完之后,再对并查集做撤销
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5e5+5;
int n,m;
int pa[maxn];
int find(int x)
{
return x==pa[x]?x:pa[x]=find(pa[x]);
}
struct Edge
{
int u,v,w,id;
bool operator<(const Edge& b) const
{
return w<b.w;
}
} edges[maxn],p[maxn];
bool cmp(Edge a,Edge b)
{
return a.id<b.id;
}
bool solve()
{
int k,x;
scanf("%d",&k);
for(int i=1; i<=k; ++i)
scanf("%d",&x),p[i]=edges[x];
sort(p+1,p+1+k);
int j;
for(int i=1; i<=k; i=j)
{
j=i;
while(j<=k&&p[i].w==p[j].w) j++;
for(int x=i; x<j; ++x)
{
int u=p[x].u,v=p[x].v;
pa[u]=u,pa[v]=v;
}
for(int x=i; x<j; ++x)
{
int u=p[x].u,v=p[x].v;
int ru=find(u),rv=find(v);
if(ru==rv) return 0;
pa[ru]=rv;
}
}
return 1;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1; i<=n; ++i) pa[i]=i;
for(int i=1; i<=m; ++i)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
edges[i]= {u,v,w,i};
}
sort(edges+1,edges+1+m);
int j;
for(int i=1; i<=m; i=j)
{
j=i;
while(j<=m&&edges[i].w==edges[j].w) j++;
for(int k=i; k<j; ++k)
{
edges[k].u=find(edges[k].u);
edges[k].v=find(edges[k].v);
}
for(int k=i; k<j; ++k)
{
int ru=find(edges[k].u),rv=find(edges[k].v);
if(ru==rv) continue;
pa[ru]=rv;
}
}
sort(edges+1,edges+1+m,cmp);
int q;
scanf("%d",&q);
while(q--)
{
puts(solve()?"YES":"NO");
}
return 0;
}
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5e5+5,maxm=5e5;
int n,m;
int ans[maxn];
int pa[maxn],depth[maxn];
struct Edge
{
int u,v,w;
} e2[maxn];
vector<Edge> e1[maxn];
struct Query
{
int u,v,id;
};
vector<Query> qu[maxn];
struct Node
{
int u,ru,v,dv;
};
int find(int x)
{
return x==pa[x]?x:find(pa[x]);
}
void merge(int u,int v)
{
int ru=find(u),rv=find(v);
if(ru!=rv)
{
if(depth[ru]>depth[rv]) swap(ru,rv);
if(depth[ru]+1>depth[rv]) depth[rv]++;
pa[ru]=rv;
}
}
stack<Node> sta;
int solve(int w,int l,int r)
{
while(!sta.empty()) sta.pop();
for(int i=l; i<=r; ++i)
{
int u=qu[w][i].u,v=qu[w][i].v;
int ru=find(u),rv=find(v);
if(ru==rv) ans[qu[w][i].id]=1;
else
{
if(depth[ru]>depth[rv]) swap(ru,rv);
sta.push({ru,pa[ru],rv,depth[rv]});
if(depth[ru]+1>depth[rv]) depth[rv]++;
pa[ru]=rv;
}
}
while(!sta.empty())
{
int u=sta.top().u,ru=sta.top().ru,v=sta.top().v,dv=sta.top().dv;
sta.pop();
pa[u]=ru;
depth[v]=dv;
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1; i<=n; ++i) pa[i]=i;
for(int i=1; i<=m; ++i)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
e1[w].push_back({u,v,0});
e2[i]= {u,v,w};
}
int q,k,x;
scanf("%d",&q);
for(int id=1; id<=q; ++id)
{
scanf("%d",&k);
for(int i=1; i<=k; ++i)
{
scanf("%d",&x);
qu[e2[x].w].push_back({e2[x].u,e2[x].v,id});
}
}
for(int i=1; i<=maxm; ++i)
{
for(auto x: e1[i-1]) merge(x.u,x.v);
int r;
for(int l=0; l<qu[i].size(); l=r)
{
r=l;
while(r<qu[i].size()&&qu[i][l].id==qu[i][r].id) r++;
solve(i,l,r-1);
}
}
for(int i=1; i<=q; ++i) puts(ans[i]?"NO":"YES");
return 0;
}