YbtOJ 「图论」第2章 最小生成树

为什么区间 dp 又咕咕咕了QAQ
于是随机抽取了一个幸运章节来做。
目前处于半摆烂状态。

例题1.繁忙都市

板子。写了下以前几乎没写过的堆优化 Prim。

code
#include<bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define se second
using namespace std; 
const int N=300;
const int M=2e5+5;
int n,m;
int head[N],cnt;
struct node{
	int nxt,to,w;
}e[M];
void add(int u,int v,int w){
	e[++cnt].nxt=head[u];e[cnt].to=v;e[cnt].w=w;head[u]=cnt;
}
int vis[N];
priority_queue<pii,vector<pii>,greater<pii> >q;
int Prim(int s)
{
	int ans=0;
	q.push(pii(0,s));
	while(!q.empty())
	{
		int w=q.top().fi,u=q.top().se;q.pop();
		if(vis[u]) continue;
		ans=max(ans,w);vis[u]=1;
		//cout<<w<<" "<<u<<endl;
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v=e[i].to;
			if(vis[v]) continue;
			q.push(pii(e[i].w,v));
		}
	}
	return ans;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1,u,v,w;i<=m;i++)
	{
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);add(v,u,w);
	}
	cout<<n-1<<" "<<Prim(1)<<endl;
	return 0;
}

例题2.新的开始

每个点往 0 号点连一条边代表建立电站,跑最小生成树。

code
#include<bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define se second
using namespace std; 
const int N=305;
const int M=1e5+5;
int n,m,a[N];
int head[N],cnt;
struct node{
	int nxt,to,w;
}e[M];
void add(int u,int v,int w){
	e[++cnt].nxt=head[u];e[cnt].to=v;e[cnt].w=w;head[u]=cnt;
}
int vis[N],tot;
priority_queue<pii,vector<pii>,greater<pii> >q;
int Prim(int s)
{
	int ans=0;
	q.push(pii(0,s));
	while(!q.empty())
	{
		int w=q.top().fi,u=q.top().se;q.pop();
		if(vis[u]) continue;
		ans+=w;vis[u]=1;tot++;
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v=e[i].to;
			if(vis[v]) continue;
			q.push(pii(e[i].w,v));
		}
	}
	return ans;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),add(i,0,a[i]),add(0,i,a[i]);
	for(int i=1;i<=n;i++)
	{
		for(int j=1,w;j<=n;j++)
		{
			scanf("%d",&w);
			if(i!=j) add(i,j,w);
		}
	}
	cout<<Prim(0)<<endl;
	//cout<<n-1<<" "<<Prim(1)<<endl;
	return 0;
}

例题3.公路建设

原先就不在最小生成树上的边,加了其他边也还是不在。
只考虑最小生成树原有的边和新加进来这条就好了。

code
#include<bits/stdc++.h>
using namespace std;
const int N=2005;
int n,m,fa[N];
struct node{
	int u,v,w;
}e[N];
int find(int x)
{
	if(fa[x]==x) return x;
	else return fa[x]=find(fa[x]);
}
int cnt,ans;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++)
	{
		cnt++;
		scanf("%d%d%d",&e[cnt].u,&e[cnt].v,&e[cnt].w);
		if(find(e[cnt].u)!=find(e[cnt].v)) 
		{
			fa[find(e[cnt].u)]=find(e[cnt].v);
			ans+=e[cnt].w;
			if(cnt==n-1) printf("%.1lf\n",ans/2.0);
			else cout<<0<<endl;
			int qwq=cnt;
			while(e[qwq].w<e[qwq-1].w&&qwq>1)
			{
				swap(e[qwq],e[qwq-1]);
				qwq--;
			}
			continue;
		}
		int qwq=cnt;
		while(e[qwq].w<e[qwq-1].w&&qwq>1)
		{
			swap(e[qwq],e[qwq-1]);
			qwq--;
		}
		//for(int i=1;i<=cnt;i++) cout<<e[i].u<<" "<<e[i].v<<" "<<e[i].w<<endl;
		ans=0;int flag=0,now=cnt;cnt=0;
		for(int i=1;i<=n;i++) fa[i]=i;
		for(int i=1;i<=now;i++)
		{
			int u=e[i].u,v=e[i].v,w=e[i].w;
			if(find(u)==find(v)) {flag=i;continue;}
			fa[find(u)]=find(v);cnt++;
			ans+=w;
		}
		while(flag<=cnt)
		{
			swap(e[flag],e[flag+1]);
			flag++;
		}
		if(cnt==n-1) printf("%.1lf\n",ans/2.0);
		else cout<<0<<endl;
	}
	return 0;
}

为什么我昨晚写的部分全都没保存上啊QAQ 简略一点再写一遍吧。

例题4.构造完全图

对于 kruskal 每次连接的两个连通块,把除这条边以外的边权都设为 w+1。

code
#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int N=1e5+5;
int n;
struct node{
	int u,v,w;
}e[N];
int fa[N],siz[N];
int find(int x)
{
	if(fa[x]==x) return x;
	else return fa[x]=find(fa[x]);
}
bool cmp(node x,node y){
	return x.w<y.w;
}
signed main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
	for(int i=1;i<n;i++) scanf("%lld%lld%lld",&e[i].u,&e[i].v,&e[i].w);
	sort(e+1,e+n,cmp);
	int ans=0;
	for(int i=1;i<n;i++)
	{
		int u=e[i].u,v=e[i].v,w=e[i].w;
		//cout<<u<<" "<<v<<" "<<w<<" qwq"<<endl;
		//cout<<find(u)<<endl;
		//cout<<find(v)<<endl;
		ans+=siz[find(u)]*siz[find(v)]*(w+1)-1;
		siz[find(u)]+=siz[find(v)];
		fa[find(v)]=find(u);
	}
	cout<<ans<<endl;
	return 0;
}

1.连接云朵

板子。连到 n-k 条边就退出。

code
#include<bits/stdc++.h>
using namespace std;
struct node{
	int u,v,w;
}e[200001];
int fa[5001],n,m,ans,cnt,k;
bool cmp(node a,node b){
    return a.w<b.w;
}
int find(int x)
{
    if(fa[x]==x) return x;
    return fa[x]=find(fa[x]);
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=m;i++)
    {
    	scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
    }
    sort(e+1,e+m+1,cmp);
    for(int i=1;i<=m;i++)
    {
        if(find(e[i].u)==find(e[i].v)) continue;
        ans+=e[i].w;
        fa[find(e[i].v)]=find(e[i].u);
        if(++cnt==n-k)
		{
			printf("%d\n",ans);
			return 0;
		}
    }
    cout<<"No Answer"<<endl;
    return 0;
}

2.序列破解

为什么我想不出这题。为什么我这么菜啊。
把选择的区间转换为前缀和相减,则选择区间 \([l,r]\) 相当于将 \(l-1\)\(r\) 连边。
要保证所有点连通,要连 \(n\) 条边。

code
#include<bits/stdc++.h>
using namespace std;
const int N=2005;
int n,cnt,fa[N];
struct node{
	int u,v,w;
}e[N*N];
bool cmp(node x,node 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()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		for(int j=i;j<=n;j++)
		{
			cnt++;
			e[cnt].u=i-1,e[cnt].v=j;
			scanf("%d",&e[cnt].w);
			//cout<<e[cnt].u<<" "<<e[cnt].v<<" "<<e[cnt].w<<endl;
		}
	}
	for(int i=1;i<=n;i++) fa[i]=i;
	sort(e+1,e+cnt+1,cmp);int tot=0,ans=0;
	for(int i=1;i<=cnt;i++)
	{
		int u=e[i].u,v=e[i].v,w=e[i].w;
		if(find(u)==find(v)) continue;
		fa[find(u)]=find(v);tot++;ans+=w;
		if(tot==n) break;
	}
	cout<<ans<<endl;
	return 0;
}

3.生物进化

题意其实就是求最小生成树每个点的祖先。
在建成的最小生成树上 dfs 统计每个点的父亲。

code
#include<bits/stdc++.h>
using namespace std;
const int N=105;
int n,cnt,qwq,tot,fa[N],head[N];
struct node1{
	int u,v,w;
}e1[N*N];
struct node{
	int nxt,to;
}e[N];
void add(int u,int v){
	e[++qwq].nxt=head[u];e[qwq].to=v;head[u]=qwq;
}
bool cmp(node1 x,node1 y){
	return x.w<y.w;
}
int find(int x)
{
	if(fa[x]==x) return x;
	else return fa[x]=find(fa[x]);
}
void dfs(int x,int f)
{
	fa[x]=f;
	for(int i=head[x];i;i=e[i].nxt)
	{
		if(e[i].to!=f) dfs(e[i].to,x);
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		for(int j=1,w;j<=n;j++)
		{
			scanf("%d",&w);
			if(i<j) e1[++cnt]={i,j,w};
		}
	}
	for(int i=1;i<=n;i++) fa[i]=i;
	sort(e1+1,e1+cnt+1,cmp);
	for(int i=1;i<=cnt;i++)
	{
		int u=e1[i].u,v=e1[i].v,w=e1[i].w;
		if(find(u)==find(v)) continue;
		add(u,v);add(v,u);
		fa[find(u)]=find(v);
		tot++; 
		if(tot==n-1) break;
	}
	dfs(1,0);
	for(int i=2;i<=n;i++) cout<<fa[i]<<endl;
	return 0;
}

4.保留道路

先按 g 升序排序,枚举 g 的最大值。把所有 g 的值小于当前最大值的边加进集合跑一遍最小生成树,求出最小的 s,更新答案。
参照例题 3,集合只存原来就在最小生成树上的边。
数据里似乎没有无解的情况。

code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=505,M=50005;
int n,m,wg,wS;
int ans=4e18;
struct node{
	int u,v,g,s;
}e[M],a[N];
bool cmp(node x,node y){
	return x.g<y.g;
}
int fa[N];
int find(int x)
{
	if(fa[x]==x) return x;
	else return fa[x]=find(fa[x]);
}
signed main()
{
	scanf("%lld%lld%lld%lld",&n,&m,&wg,&wS);
	for(int i=1;i<=m;i++)
	{
		scanf("%lld%lld%lld%lld",&e[i].u,&e[i].v,&e[i].g,&e[i].s);
	}
	for(int i=1;i<=n;i++) fa[i]=i;
	sort(e+1,e+m+1,cmp);
	int now=0;
	for(int i=1;i<=m;i++)
	{
		a[++now]=e[i];
		int u=e[i].u,v=e[i].v,g=e[i].g,s=e[i].s;
		if(find(u)!=find(v))
		{
			fa[find(u)]=find(v);
			int qwq=now;
			while(a[qwq].s<a[qwq-1].s&&qwq>1)
			{
				//cout<<"qaq"<<endl;
				swap(a[qwq],a[qwq-1]);qwq--;
			}
			if(now==n-1) 
			{
				//cout<<g<<" "<<a[now].s<<"qwq"<<endl;
				ans=min(ans,wg*g+wS*a[now].s);
			}
			//cout<<now<<" "<<ans<<endl;
			continue;
		}
		int qwq=now;
		while(a[qwq].s<a[qwq-1].s&&qwq>1)
		{
			swap(a[qwq],a[qwq-1]);qwq--;
		}
		qwq=now;now=0;int flag=0;
		for(int j=1;j<=n;j++) fa[j]=j;
		for(int j=1;j<=qwq;j++)
		{
			if(find(a[j].u)==find(a[j].v)) {flag=j;continue;}
			fa[find(a[j].u)]=find(a[j].v);now++;
		}
		while(flag<qwq) swap(a[flag],a[flag+1]),flag++;
		if(now==n-1) ans=min(ans,wg*g+wS*a[now].s);
		//cout<<now<<endl;
	}
	if(ans==(long long)4e18) cout<<"-1"<<endl;
	else cout<<ans<<endl;
	return 0;
}
/*
4 3
1 3
4 2 2 2
3 2 8 1
3 2 4 10
*/

5.最小距离和

把点分别按照 x,y,z 的值排一次序。
发现最近的点必然相邻,因此可以只将排序后相邻的点连边。
怎么又犯最小生成树边权不排序这种sb错误啊。

code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+5;
int n,fa[N],cnt;
struct node{
	int x,y,z,id;
}a[N];
struct node1{
	int u,v,w;
}e[N];
bool cmpx(node l,node r) {return l.x<r.x;}
bool cmpy(node l,node r) {return l.y<r.y;}
bool cmpz(node l,node r) {return l.z<r.z;}
bool cmp(node1 l,node1 r) {return l.w<r.w;} 
int dis(int c,int d){
	node x=a[c],y=a[d];
	return min(min(abs(x.x-y.x),abs(x.y-y.y)),abs(x.z-y.z));
}
int find(int x)
{
	if(fa[x]==x) return x;
	else return fa[x]=find(fa[x]);
}
signed main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) fa[i]=i,scanf("%lld%lld%lld",&a[i].x,&a[i].y,&a[i].z),a[i].id=i;
	sort(a+1,a+n+1,cmpx);
	for(int i=1;i<n;i++) if(dis(i,i+1)==a[i+1].x-a[i].x) e[++cnt]={a[i].id,a[i+1].id,dis(i,i+1)};
	sort(a+1,a+n+1,cmpy);
	for(int i=1;i<n;i++) if(dis(i,i+1)==a[i+1].y-a[i].y) e[++cnt]={a[i].id,a[i+1].id,dis(i,i+1)};
	sort(a+1,a+n+1,cmpz);
	for(int i=1;i<n;i++) if(dis(i,i+1)==a[i+1].z-a[i].z) e[++cnt]={a[i].id,a[i+1].id,dis(i,i+1)};
	int now=0,ans=0;
	sort(e+1,e+cnt+1,cmp);
	for(int i=1;i<=cnt;i++)
	{
		int u=e[i].u,v=e[i].v,w=e[i].w;
		if(find(u)==find(v)) continue;
		now++;ans+=w;
		fa[find(u)]=find(v);
		if(now==n-1) {cout<<ans<<endl;return 0;}
	}
	return 0;
}

下一步该写点什么呢。

posted @ 2022-08-17 20:55  樱雪喵  阅读(116)  评论(0编辑  收藏  举报