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

link

给定一个 \(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

link

有一个未知的 \(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

link

动物王国中有三类动物 \(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

link

给定 \(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;
}
posted @ 2023-11-24 17:25  SFlyer  阅读(11)  评论(0编辑  收藏  举报