并查集+最小生成树 学习笔记+杂题 4

图论系列:

前言:

相关题单:戳我

算法讲解:戳我

AT_abc218_e [ABC218E] Destruction

最大生成树。

代码:

略\fad

AT_abc235_e [ABC235E] MST + 1

把原本图的边和询问的边混在一起,跑最小生成树的时候看是否有可能将询问的某条边加上(但不能真的加上),可能就是 Yes ,否则是 No

代码:

inline int max(int x,int y){return x>y?x:y;}
inline int min(int x,int y){return x>y?y:x;}
const int M=4e5+5;
int n,m,q,len;
int fa[M],ans[M];

struct N{
	int u,v,w,id;
	bool operator <(const N &o) const
	{
		return w<o.w;
	}
};N p[M];

inline int find(int x)
{
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>m>>q,len=m+q;
	for(int i=1;i<=n;++i) fa[i]=i;
	for(int i=1;i<=m;++i) cin>>p[i].u>>p[i].v>>p[i].w;
	for(int i=m+1;i<=m+q;++i) cin>>p[i].u>>p[i].v>>p[i].w,p[i].id=i-m;//混一堆了
	sort(p+1,p+len+1);
	for(int i=1,fx,fy;i<=len;++i)
	{
		fx=find(p[i].u),fy=find(p[i].v);
		if(fx==fy) continue;
		if(!p[i].id) fa[fx]=fy;
		else ans[p[i].id]=1;//观察是否能加入,但是不真的加入
	}
	for(int i=1;i<=q;++i)
	{
		if(ans[i]) cout<<"Yes\n";
		else cout<<"No\n";
	}
	return 0;
}

AT_abc282_e [ABC282E] Choose Two and Eat One

观察到 \(n \leq 500\) ,可以 \(O(n^2)\) 暴力建边之后跑最大生成树。

代码:

const int M=505;
int n,mod,len,ans;
int a[M],fa[M];

struct N{
	int u,v,w;
	bool operator <(const N &o) const
	{
		return o.w<w;
	}
};N p[M*M];

inline int quick(int a,int n)
{
	int res=1;
	while(n)
	{
		if(n&1) res=res*a%mod;
		n>>=1,a=a*a%mod;
	}
	return res;
}

inline int find(int x)
{
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>mod;
	for(int i=1;i<=n;++i) cin>>a[i],fa[i]=i;
	for(int i=1;i<=n;++i)
	{
		for(int j=i+1;j<=n;++j)
		{
			p[++len].u=i,p[len].v=j,p[len].w=(quick(a[i],a[j])+quick(a[j],a[i]))%mod;
		}
	}//暴力建边
	sort(p+1,p+len+1);//从大到小
	for(int i=1,fx,fy;i<=len;++i)
	{
		fx=find(p[i].u),fy=find(p[i].v);
		if(fx==fy) continue;
		fa[fx]=fy,ans+=p[i].w;
	}
	cout<<ans<<"\n";
	return 0;
}

AT_arc076_b [ABC065D] Built?

我记得洛谷上有道题是询问的三维?这里只有两维更简单,那么\(x\) 排一次序,然后按 \(y\) 排一次序,将相邻的两个城市连一条边(此时一定是最优的),这样只会建 \(2(n-1)\) 条边,建完后跑最小生成树即可。

代码:

const int M=1e5+5;
int n;
int fa[M];

int cnt=0;
struct node{
	int x,y,id;
};node s[M];
struct N{
	int u,v,w;
	bool operator < (const N &o) const
	{
		return w<o.w;
	}
};N p[M<<1];

inline bool cmp1(node a,node b){return a.x<b.x;}
inline bool cmp2(node a,node b){return a.y<b.y;}

inline int find(int x)
{
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;++i) cin>>s[i].x>>s[i].y,s[i].id=i,fa[i]=i;
	sort(s+1,s+n+1,cmp1);
	for(int i=2;i<=n;++i)
	{
		++cnt;
		p[cnt].u=s[i-1].id,p[cnt].v=s[i].id,p[cnt].w=min(abs(s[i].x-s[i-1].x),abs(s[i].y-s[i-1].y));
	}
	sort(s+1,s+n+1,cmp2);
	for(int i=2;i<=n;++i)
	{
		++cnt;
		p[cnt].u=s[i-1].id,p[cnt].v=s[i].id,p[cnt].w=min(abs(s[i].x-s[i-1].x),abs(s[i].y-s[i-1].y));
	}
	sort(p+1,p+cnt+1);
	int ans=0;
	for(int i=1,fx,fy;i<=cnt;++i)
	{
		fx=find(p[i].u),fy=find(p[i].v);
		if(fx==fy) continue;
		fa[fx]=fy,ans+=p[i].w;
	}
	cout<<ans<<"\n";
	return 0;
}

AT_abc270_f [ABC270F] Transportation

参照前面三个题单,已经有建超级源点的题目了,这题也一样,可以建两个超级源点,分别表示建机场和港口,每个点向这两个点连边,边权就是给定的值,边数为 \(2*n+m\) ,然后跑最小生成树即可。

但是这道题不一样的地方在于它整个图都可以不建机场或者港口,所以不一定两个超级源点需要连通,所以我们枚举四种情况:两个都会建,只会建机场,只会建港口,都不会建,跑四遍最小生成树,去权值最小的即可。

代码:

const int M=1e6+5;
int n,m,t1,t2;
int fa[M];

int cnt=0;
struct N{
	int u,v,w,opt;
};N p[M];
inline bool cmp(N a,N b){return a.w<b.w;}

inline int find(int x)
{
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}

inline int solve(int flag1,int flag2)
{
	int res=0,ans=0;
	for(int i=1;i<=t2;++i) fa[i]=i;
	for(int i=1,fx,fy;i<=cnt;++i)
	{
		if(p[i].opt==1&&!flag1) continue;
		if(p[i].opt==2&&!flag2) continue;
		fx=find(p[i].u),fy=find(p[i].v);
		if(fx==fy) continue;
		fa[fx]=fy,++res,ans+=p[i].w;
	}
	if(res!=n+flag1+flag2-1) return inf;
	else return ans;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>m,t1=n+1,t2=n+2;
	for(int i=1;i<=n;++i) cin>>p[++cnt].w,p[cnt].u=i,p[cnt].v=t1,p[cnt].opt=1;
	for(int i=1;i<=n;++i) cin>>p[++cnt].w,p[cnt].u=i,p[cnt].v=t2,p[cnt].opt=2;
	for(int i=1;i<=m;++i) ++cnt,cin>>p[cnt].u>>p[cnt].v>>p[cnt].w;
	sort(p+1,p+cnt+1,cmp);
	cout<<min(min(solve(0,0),solve(0,1)),min(solve(1,0),solve(1,1)))<<"\n"; 
	return 0;
}

AT_arc029_3 [ARC029C] 高橋君と国家

和上一题一样的,建超级源点,同每个点连边,表示在这个城市建交易所。这题就需要使超级源点连通了,因为图上至少都需要一个交易所,直接跑最小生成树即可。(woc,我好厉害,我竟然是先钦定每一个地方都建交易所,然后合并的时候是个集合只用建一个即可,由于要代价最小,那么就建权值最小的那个)

代码:

const int M=2e5+5;
int n,m,ans;
int a[M],fa[M];

struct N{
	int u,v,w;
	inline bool operator <(const N &o) const
	{
		return w<o.w;
	}
};N p[M];
inline int find(int x)
{
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;++i) cin>>a[i],fa[i]=i,ans+=a[i];
	for(int i=1;i<=m;++i) cin>>p[i].u>>p[i].v>>p[i].w;
	sort(p+1,p+m+1);
	for(int i=1,fx,fy;i<=m;++i)
	{
		fx=find(p[i].u),fy=find(p[i].v);
		if(fx==fy) continue;
		if(p[i].w<max(a[fx],a[fy]))
		{
			if(a[fx]<a[fy]) swap(fx,fy);
			fa[fx]=fy;
			ans=ans+p[i].w-max(a[fx],a[fy]);
		}
	}
	cout<<ans<<"\n";
	return 0;
}

AT_arc026_4 [ARC026D] 道を直すお仕事

学生输出 nan 得满分,属于是究极不可以总司令了。

这题其实也不是并查集ing。

后面又加了几道题。

AT_abc350_d [ABC350D] New Friends

首先先将给定的边两两点进行合并,在一个连通块中,根据题目的要求,那么任意两个点之间都会存在一条边,对于一个大小为 \(siz_x\) 的连通块,其应该需要连 \(siz_x*(siz_x-1)/2\) 条边(无向边所以除二),然后再合并的时候统计一下当前连通块已经含有的边数。

最后遍历每一个连通块,用应该含有的边数减去已经含有的边数就是答案。

代码:

const int M=2e5+5;
int n,m;
int a[M],siz[M],b[M],fa[M],cnt[M];
//siz是集合大小,cnt是集合内含的边数
inline int find(int x)
{
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
	for(int i=1,x,y;i<=m;i++)
	{
		cin>>a[i]>>b[i];
		x=find(a[i]),y=find(b[i]);
		if(x==y)
		{
			++cnt[y];
			continue;
		}
		fa[x]=y,siz[y]+=siz[x],siz[x]=0,cnt[y]+=cnt[x]+1,cnt[x]=0;
	}
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		if(i==fa[i])
		{
			ans+=siz[i]*(siz[i]-1)/2-cnt[i];
		}
	}
	cout<<ans<<"\n";
	return 0;
}

AT_abc181_f [ABC181F] Silver Woods

二分答案题,因为问的是最大半径,自然圆的半径越大越可能会被钉子拦住,所以是否可从起点到达终点对于圆的半径是有单调性的,所以考虑二分出圆的半径。

对于一个半径,由于 \(n\) 范围较小,将每两个点之间的距离算出来,若直径小于这个距离那么就滚的过去,再设两个虚点,一个表示起点,一个表示终点,合并完所有能合并的点之后,观察起点和终点是否连通。

代码:

const int M=5e5+5;
int n;
int fa[M];
double x[M],y[M];


inline double dist(int a,int b)
{
	return sqrt((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b])); 
}
inline int find(int x)
{
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
 } 
inline int check(double x)
{
	for(int i=1;i<=n+2;i++) fa[i]=i;//预留两个空间 
	for(int i=1;i<=n;i++)
	{
		if(y[i]+x>=100-x) fa[find(i)]=find(n+1); 
	}
	for(int i=1;i<=n;i++)
	{
		if(y[i]-x<=x-100) fa[find(i)]=find(n+2);
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(dist(i,j)<=2*x)
			{
				fa[find(i)]=find(j);
			}
		}
	}
	return find(n+1)!=find(n+2);
}

signed main()
{
	cin>>n;
	for(int i=1;i<=n;i++) cin>>x[i]>>y[i];
	double l=0,r=100;
	while(r-l>=eps)
	{
		double mid=(l+r)/2;
		if(check(mid)) l=mid;
		else r=mid;
	}
	printf("%.12Lf",l);
	return 0;
}

AT_abc120_d [ABC120D] Decayed Bridges

经典思路,对于删边考虑正难则反。让时间倒流,那么一开始没有边相连,割裂的点对数就是 \(n*(n-1)/2\)

对于加入一条边 \(x \to y\)\(x,y\) 所在连通块的大小为 \(siz_x,siz_y\) ,那么割裂的点对增加 \(siz_x*(siz_x-1)/2+siz_y*(siz_y-1)/2\) (相当于是将这两个小连通块删去了),设 \(siz=siz_x+siz_y\) 那么点对减少 \(siz*(siz-1)/2\) (计算新生成的大连通块减少的点对数量)。

代码:

const int M=1e5+5;
int n,m;
int fa[M],ans[M],siz[M];

struct N{
	int u,v;
};N p[M];

inline int find(int x)
{
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
	for(int i=1;i<=m;i++) cin>>p[i].u>>p[i].v;
	
	int res=0;//由于是按照从前向后删边的,所以从后向前加边 
	for(int i=m;i>=1;i--)
	{
		int x=find(p[i].u),y=find(p[i].v);
		if(x==y)
		{
			ans[i]=res;continue;
		}
		fa[x]=y;
		res-=(siz[x]*(siz[x]-1)/2)+(siz[y]*(siz[y]-1)/2);
		siz[y]+=siz[x];
		res+=siz[y]*(siz[y]-1)/2;
		ans[i]=res;
	}
	res=n*(n-1)/2;
	for(int i=2;i<=m+1;i++) cout<<res-ans[i]<<"\n";
	return 0;
}

AT_keyence2019_e Connecting Cities

Boruvka 最小生成树板子题。

代码后面补。

posted @ 2024-11-12 22:08  call_of_silence  阅读(5)  评论(0编辑  收藏  举报