生成树和笛卡尔树专题

A. [COCI2009-2010#7] SVEMIR
这个题其实没啥,没看题解,自己口胡的做法,就直接把x,y,z三个方向排序当成一个边,总边数就是3n-3吧,然后就是朴素的Kruskal,直接跑完即可。

Code
#include<bits/stdc++.h>
using namespace std;
int n,cnt,ans,cntt;
const int N=1e5+5;
struct point{
	int x,y,z,id;
}a[N];
struct edge{
	int u,v,w;
}e[N*3]; 
int fa[N];
bool cmp1(point a,point b){
	return a.x<b.x;
}
bool cmp2(point a,point b){
	return a.y<b.y;
}
bool cmp3(point a,point b){
	return a.z<b.z;
}
bool cmp(edge a,edge b){
	return a.w<b.w;
}
int find(int x){
	if(fa[x]==x)
		return x;
	else 
		return fa[x]=find(fa[x]);
}
int main(){
	//首先任何两点之间都可以直接建造
	//目标是建造出一颗树
	//直接考虑优化边数
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i].x>>a[i].y>>a[i].z,a[i].id=i;
	sort(a+1,a+n+1,cmp1);
	for(int i=2;i<=n;i++)
		e[++cnt].u=a[i-1].id,e[cnt].v=a[i].id,e[cnt].w=a[i].x-a[i-1].x;
	sort(a+1,a+n+1,cmp2);
	for(int i=2;i<=n;i++)
		e[++cnt].u=a[i-1].id,e[cnt].v=a[i].id,e[cnt].w=a[i].y-a[i-1].y;
	sort(a+1,a+n+1,cmp3);
	for(int i=2;i<=n;i++)
		e[++cnt].u=a[i-1].id,e[cnt].v=a[i].id,e[cnt].w=a[i].z-a[i-1].z;
	sort(e+1,e+cnt+1,cmp);
//	for(int i=1;i<=cnt;i++){
//		cout<<e[i].u<<" "<<e[i].v<<" "<<e[i].w<<"\n";
//	} 
	for(int i=1;i<=n;i++)
		fa[i]=i;
	for(int i=1;i<=cnt;i++){
		int x=find(e[i].u),y=find(e[i].v);
		if(x==y)
			continue;
		fa[x]=y;
		ans+=e[i].w;
		cntt++;
		if(cntt==n-1)
			break;
	}
	cout<<ans<<"\n";
} 

B. [SCOI2012] 滑雪
听说很多人用的都是Prim,但是显然也可以用Kruskal,对于第一问来讲,就是一个从高向低直接跑个dfs/bfs即可
对于第二问,可以将第一问的大法师中走过的边直接选到一个边集中,然后跑一遍Kruskal。边的排序以高度为第一关键词,边权为第二关键词。这样就能保证拓展的点最多,进而再用最小生成树求最短距离。

Code
#include<bits/stdc++.h>
using namespace std;
long long n,m,cnt,cntt,ans,anss;
const int N=1e5+5; 
int h[N],fa[N];
struct Edge{
	int u,v,w;
}edge[N*20],edge1[N*20];
bool cmp(Edge a,Edge b){
	if(h[a.v]!=h[b.v])
		return h[a.v]>h[b.v];
	return a.w<b.w;
}
vector<int> e[N],e2[N]; 
bool flag[N];
int find(int x){
	if(x==fa[x])
		return x;
	else
		return fa[x]=find(fa[x]);
}
void dfs(int x,int faa){
	if(flag[x]==0)
		anss++;
	flag[x]=1;
	for(int i=0;i<e[x].size();i++){
		edge1[++cntt].u=x;
		edge1[cntt].v=e[x][i];
		edge1[cntt].w=e2[x][i];
		if(flag[e[x][i]])
			continue;
		dfs(e[x][i],x);
	}
}
signed main(){
	//只能走向比自己低的点 
	//题目相当于构建一棵最小生成树
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>h[i];
	for(int i=1;i<=m;i++){
		int u,v,w;
		cin>>u>>v>>w;
		if(h[u]==h[v]){
			edge[++cnt].u=u,edge[cnt].v=v;
			edge[cnt].w=w;
			edge[++cnt].u=v,edge[cnt].v=u;
			edge[cnt].w=w;
		}
		if(h[u]<h[v]){
			edge[++cnt].u=v,edge[cnt].v=u;
			edge[cnt].w=w;
		}
		if(h[u]>h[v]){
			edge[++cnt].u=u,edge[cnt].v=v;
			edge[cnt].w=w;
		}
	}
	for(int i=1;i<=cnt;i++){
		e[edge[i].u].push_back(edge[i].v);
		e2[edge[i].u].push_back(edge[i].w);
	}
	dfs(1,0);
	cout<<anss<<" ";
	for(int i=1;i<=n;i++)
		fa[i]=i;
	sort(edge1+1,edge1+cntt+1,cmp);
	for(int i=1;i<=cnt;i++){
		int x=find(edge1[i].u),y=find(edge1[i].v);
		if(x==y)
			continue;
		fa[y]=x;
		ans+=edge1[i].w;
		cntt++;
		if(cntt==anss-1)
			break;
	}
	cout<<ans<<"\n";
}

C. [USACO01OPEN] Earthquake
观察数据发现n \(\le\) 400,跑一次的最小生成树带来的时间复杂度的负担显然是很小的,此时时间复杂度为O(nlogn),显然完全够一个二分答案的时间复杂度,答案的值域在[0,f]之间,而答案需要保留四位小数,可以直接拿小数跑,也可以将答案乘以10000跑整数。

Code
#include<bits/stdc++.h>
using namespace std;
int n,m,f;
const int N=1e4+5;
const double eps=1e-9;
int c[N],t[N],u[N],v[N],fa[405];
struct edge{
	int u,v;
	double w;
}e[N];
bool cmp(edge a,edge b){
	return a.w<b.w;
}
int find(int x){
	if(fa[x]==x)
		return x;
	else
		return fa[x]=find(fa[x]);
}
bool check(double x){
	double xx=x,sum=0;
	int cntt=0;
	for(int i=1;i<=m;i++){
		e[i].u=u[i],e[i].v=v[i];
		e[i].w=1.0*(xx*t[i]+c[i]);
	}
//	for(int i=1;i<=m;i++)
//		cout<<e[i].u<<" "<<e[i].v<<" "<<e[i].w<<"\n";
	sort(e+1,e+m+1,cmp);
	for(int i=1;i<=n;i++)
		fa[i]=i;
	for(int i=1;i<=m;i++){
		int x=find(e[i].u),y=find(e[i].v);
		if(x==y)
			continue;
		fa[y]=x;
		cntt++;
		sum+=e[i].w;
		if(cntt==n-1)
			break;
	}
	if(f>sum)
		return false;
	else
		return true;
}
int main(){
	cin>>n>>m>>f;
	for(int i=1;i<=m;i++)
		cin>>u[i]>>v[i]>>c[i]>>t[i];
	double l=0,r=f,mid;
	while(r>l+eps){
		mid=(l+r)/2;
		if(check(mid))
			r=mid-eps;
		else
			l=mid+eps;
	}
	cout<<fixed<<setprecision(4)<<l<<"\n";
}

D. [蓝桥杯 2023 省 A] 网络稳定性
最大生成树之后直接跑\(Lca\)即可

Code
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+10;
int n,m,q,u,v,fa[N],a[N],f[N][20],dep[N];
bool vis[N];
vector<int> e[N];
struct edge{
	int u,v,w;
}e1[N];
int find(int x){
	if(fa[x]==x)
		return x;
	else
		return fa[x]=find(fa[x]);
}
bool cmp(edge a,edge b){
	return a.w>b.w;
}
void dfs(int x,int fa){
	vis[x]=1;
	f[x][0]=fa;
	dep[x]=dep[fa]+1;
	for(int i=0;i<e[x].size();i++){
		if(e[x][i]==fa)
			continue;
		dfs(e[x][i],x);
	}
}
int lca(int u,int v){
	if(dep[u]<dep[v])
		swap(u,v);
	int d=dep[u]-dep[v];
	for(int i=19;i>=0;--i)
		if(d>>i&1)
			u=f[u][i];
	if(u==v)
		return u;
	for(int i=19;i>=0;--i)
		if(f[u][i]!=f[v][i])
			u=f[u][i],v=f[v][i];
	return f[u][0];
}
int main(){
	cin>>n>>m>>q;
	for(int i=1;i<=n+m;i++)
		fa[i]=i;
	for(int i=1;i<=m;++i)
		cin>>e1[i].u>>e1[i].v>>e1[i].w;
	sort(e1+1,e1+m+1,cmp);
	int cur=n;
	for(int i=1;i<=m;++i){
		int u=e1[i].u,v=e1[i].v,w=e1[i].w;
		u=find(u),v=find(v);
		if(u==v)
			continue;
		cur+=1;
		fa[u]=fa[v]=cur;
		e[cur].push_back(u);
		e[cur].push_back(v);
		a[cur]=w;
	}
	for(int i=cur;i>=1;i--)
		if(!vis[i])
			dfs(i,0);
	for(int j=1;j<20;++j)
		for(int i=1;i<=cur;++i)
			f[i][j]=f[f[i][j-1]][j-1];
	while(q--){
		cin>>u>>v;
		if(find(u)!=find(v)){
			puts("-1");
			continue;
		}
		cout<<a[lca(u,v)]<<"\n";
	}
	return 0;
}

E. [THUPC2022 初赛] 最小公倍树

想了很久,无果,直接看了题解,发现建边方式挺妙的,就是按照枚举公因数的方式建边,设在[l,r]中是x的倍数最小的数为w将所有是x的倍数的数和w建边最优,所以跑Kruskal即可。

Code
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int l,r,cnt,fa[N],cntt;
#define ll long long 
ll ans;
struct edge{
	int u,v;
	ll w;
}e[1300005];
bool cmp(edge x,edge y){
	return x.w<y.w;
}
int find(int x){
	if(fa[x]==x)
		return x;
	else
		return fa[x]=find(fa[x]);
}
int main(){
	//其实感觉最后一步就是直接跑一个最小生成树
	//但是要看具体怎么建边
	//(4,6) (4,8) (4,10) (4,12)
	//(3,6) (3,9) (3,12)
	//(4,8) (4,12)
	//(5,10)
	//(6,12)
	//(4,6) (4,8) (4,10) (4,12) (3,6) (3,9) (3,12)
	//(5,10) (6,12)
	cin>>l>>r;
	for(int i=1;i<=r;i++){
		int k=(l-1)/i+1;
		int zuo=k*i;
		for(int j=zuo+i;j<=r;j+=i){
			e[++cnt].u=zuo;
			e[cnt].v=j;
			e[cnt].w=1ll*j*zuo/i;
		}
	}
	sort(e+1,e+cnt+1,cmp);
//	for(int i=1;i<=cnt;i++){
//		cout<<e[i].u<<" "<<e[i].v<<" "<<e[i].w<<"\n";
//	}
	for(int i=l;i<=r;i++)
		fa[i]=i;
	for(int i=1;i<=cnt;i++){
		int x=find(e[i].u),y=find(e[i].v);
		if(x==y)
			continue;
		cntt++;
		fa[y]=x;
		ans+=e[i].w;
		if(cntt==r-l)
			break;
	}
	cout<<ans<<"\n";
}
posted @ 2025-02-05 17:29  Dengyouk  阅读(28)  评论(0)    收藏  举报