Disjoint set union
普通 dsu
Code
int fa[N];
int ff(int x){
return fa[x]==x?x:fa[x]=ff(fa[x]);
}
void merge(int x,int y){
x=ff(x);
y=ff(y);
if (x!=y){
fa[x]=y;
}
}
for (int i=0; i<n; i++){
fa[i]=i;
}
probs
luogu 4185
给定一个 \(N\) 个点,\(N-1\) 条边的树(有边权)。\(Q\) 次询问,问从 \(v_i\) 开始只能经过边权大于等于 \(k_i\) 的边,能到达多少点(\(v_i\) 不算)。
\(1\le N,Q\le 10^5\),边权和 \(k\) 的值域为 \([1,10^9]\)。
可以先离线,边按相关性从大到小加,询问也是从大到小回答。答案就是连通块大小减一。
Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5+5;
int n,q,fa[N],sz[N],ans[N];
struct qy {
int k,v,id;
} a[N];
struct edge {
int u,v,w;
} e[N];
bool cmpe(edge x,edge y){
return x.w>y.w;
}
bool cmpa(qy x,qy y){
return x.k>y.k;
}
int ff(int x){
return x==fa[x]?x:fa[x]=ff(fa[x]);
}
void merge(int x,int y){
x=ff(x);
y=ff(y);
if (x!=y){
fa[y]=x;
sz[x]+=sz[y];
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>q;
for (int i=1; i<=n; i++){
fa[i]=i;
sz[i]=1;
}
for (int i=1; i<n; i++){
cin>>e[i].u>>e[i].v>>e[i].w;
}
for (int i=1; i<=q; i++){
cin>>a[i].k>>a[i].v;
a[i].id=i;
}
sort(e+1,e+n,cmpe);
sort(a+1,a+q+1,cmpa);
int pos=1;
for (int i=1; i<=q; i++){
while (pos<n && a[i].k<=e[pos].w){
merge(e[pos].u,e[pos].v);
pos++;
}
ans[a[i].id]=sz[ff(a[i].v)]-1;
}
for (int i=1; i<=q; i++){
cout<<ans[i]<<endl;
}
return 0;
}
带权 dsu
Code
int ff(int x){
if (x!=fa[x]){
int t=fa[x];
fa[x]=ff(fa[x]);
sum[x]+=sum[t];
}
return fa[x];
}
probs
hdu 3038
有一个未知的 \(n\) 个整数的数列 \(x\),\(m\) 个信息
a b v
:\(\sum_{i=a}^{b} x_i=v\)。求有多少个信息个前面给出的信息不符合。多组测试数据,\(1\le n \le 2\cdot 10^5,1\le m \le 4\cdot 10^4\)。
条件转化为 \(\sum_{i=1}^{b} x_i-\sum_{i=1}^{a-1} x_i=v\),带权并查集维护。
Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5+5;
int fa[N],sum[N],ans;
int ff(int x){
if (x!=fa[x]){
int t=fa[x];
fa[x]=ff(fa[x]);
sum[x]+=sum[t];
}
return fa[x];
}
void merge(int x,int y,int v){
int fx=ff(x);
int fy=ff(y);
if (fx==fy){
if (sum[x]-sum[y]!=v){
ans++;
}
}
else{
fa[fx]=fy;
sum[fx]=sum[y]-sum[x]+v;
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n,m;
while (cin>>n>>m){
for (int i=0; i<N; i++){
fa[i]=i;
sum[i]=0;
}
ans=0;
for (int i=0; i<m; i++){
int a,b,v;
cin>>a>>b>>v;
a--;
merge(a,b,v);
}
cout<<ans<<endl;
}
return 0;
}
luogu 2024
动物王国中有三类动物 \(A,B,C\),\(A\) 吃 \(B\),>\(B\) 吃 \(C\),\(C\) 吃 \(A\)。
现有 \(N\) 个动物,以 \(1 \sim N\) 编号。每个动物都是 \(A,B,C\) 中的一种,但是我们并>不知道它到底是哪一种。
有人用两种说法对这 \(N\) 个动物所构成的食物链关系进行描述:
- 第一种说法是
1 X Y
,表示 \(X\) 和 \(Y\) 是同类。- 第二种说法是
2 X Y
,表示 \(X\) 吃 \(Y\)。此人对 \(N\) 个动物,用上述两种说法,一句接一句地说出 \(K\) 句话,这 \(K\) 句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
- 当前的话与前面的某些真的话冲突,就是假话;
- \(X>N\) 或 \(Y>N\),就是假话;
- 当前的话表示 \(X\) 吃 \(X\),就是假话。
你的任务是根据给定的 \(N\) 和 \(K\) 句话,输出假话的总数。
\(1\le N\le 5\cdot 10^4,1\le K\le 10^5\)。
一个动物和另一个动物一共有 \(3\) 种状态:\(0\) 相等,\(1\) 吃掉,\(2\) 被吃。
设三只动物为 \(x,y,z\),\(f(x,y)\) 代表 \(x,y\) 之间的关系。则可以发现 \(f(x,z)=(f(x,y)+f(y,z)) \mod 3\)。可以用带权并查集维护。
Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5+5;
int fa[N],sum[N],ans;
int ff(int x){
if (x!=fa[x]){
int t=fa[x];
fa[x]=ff(fa[x]);
sum[x]+=sum[t];
sum[x]%=3;
}
return fa[x];
}
void merge(int x,int y,int f){
int fx=ff(x);
int fy=ff(y);
if (fx==fy){
if ((sum[x]-sum[y]+3)%3!=f){
ans++;
}
}
else{
fa[fx]=fy;
sum[fx]=(sum[y]-sum[x]+f)%3;
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n,m;
cin>>n>>m;
for (int i=0; i<N; i++){
fa[i]=i;
}
for (int i=0; i<m; i++){
int f,a,b;
cin>>f>>a>>b;
f--;
if (a>n || b>n || f==1 && a==b){
ans++;
}
else{
merge(a,b,f);
}
}
cout<<ans<<endl;
return 0;
}
可撤销并查集
不能路径压缩。
Code
struct dsu {
int n=0,top=0;
int fa[N],sz[N],stk[N];
void ins(){
n++;
fa[n]=n;
sz[n]=1;
}
void init(int x){
while (x--){
ins();
}
}
int ff(int x){
return fa[x]==x?x:ff(fa[x]);
}
void uni(int x,int y){
x=ff(x);
y=ff(y);
if (x==y){
return;
}
if (sz[x]<sz[y]){
swap(x,y);
}
stk[++top]=y;
fa[y]=x;
sz[x]+=sz[y];
}
bool sm(int x,int y){
return ff(x)==ff(y);
}
void del(){
if (!top){
return;
}
int y=stk[top--];
sz[fa[y]]-=sz[y];
fa[y]=y;
}
void dto(int x){
while (top>x){
del();
}
}
};
probs
luogu 3402
给定 \(n\) 个集合,第 \(i\) 个集合内初始状态下只有一个数,为 \(i\)。
有 \(m\) 次操作。操作分为 \(3\) 种:
1 a b
合并 \(a,b\) 所在集合;
2 k
回到第 \(k\) 次操作(执行三种操作中的任意一种都记为一次操作)之后的状态;
3 a b
询问 \(a,b\) 是否属于同一集合,如果是则输出 \(1\),否则输出 \(0\)。\(1\le n\le 10^5,1\le m\le 2\cdot 10^5\)。
可以直接用可撤销并查集。
先离线。\(1,3\) 操作 \(i-1\) 到 \(i\) 连边,\(2\) 操作 \(k\) 到 \(i\) 连边。那么一个状态就是从 \(0\) 到这个点的所有操作后的状态。可以 dfs,维护并查集。
Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5+5;
struct dsu {
int n=0,top=0;
int fa[N],sz[N],stk[N];
void ins(){
n++;
fa[n]=n;
sz[n]=1;
}
void init(int x){
while (x--){
ins();
}
}
int ff(int x){
return fa[x]==x?x:ff(fa[x]);
}
void uni(int x,int y){
x=ff(x);
y=ff(y);
if (x==y){
return;
}
if (sz[x]<sz[y]){
swap(x,y);
}
stk[++top]=y;
fa[y]=x;
sz[x]+=sz[y];
}
bool sm(int x,int y){
return ff(x)==ff(y);
}
void del(){
if (!top){
return;
}
int y=stk[top--];
sz[fa[y]]-=sz[y];
fa[y]=y;
}
void dto(int x){
while (top>x){
del();
}
}
} d;
int n,m,t[N],a[N],b[N],op[N],ans[N];
vector<int> g[N];
void dfs(int u){
t[u]=d.top;
if (op[u]==1){
d.uni(a[u],b[u]);
}
if (op[u]==3){
ans[u]=d.sm(a[u],b[u]);
}
for (auto v : g[u]){
dfs(v);
}
d.dto(t[u]);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m;
d.init(n);
for (int i=1; i<=m; i++){
cin>>op[i];
if (op[i]==1 || op[i]==3){
cin>>a[i]>>b[i];
g[i-1].push_back(i);
}
else{
cin>>a[i];
g[a[i]].push_back(i);
}
}
dfs(0);
for (int i=1; i<=m; i++){
if (op[i]==3){
cout<<ans[i]<<endl;
}
}
return 0;
}