一本通 提高篇——最小生成树

#10064. 「一本通 3.1 例 1」黑暗城堡

没写

#10065. 「一本通 3.1 例 2」北极通讯网络

在这里插入图片描述
在这里插入图片描述
代码

#include "stdio.h"
#include "algorithm"
#include "string.h"
#include "iostream"
#include "math.h"
using namespace std;
int n,m,a[505];
double px[505],py[505];
struct ppp
{
	int x,y;
	double s;
	bool operator  <(const ppp &z)const
	{
		return s<z.s;
	}
} p[250025];
int find(int x)
{
	if(a[x]==0) a[x]=x;
	if(a[x]==x) return x;
	return a[x]=find(a[x]);
}
int main()
{
	int n,m,l=0;
	cin>>n>>m;
	for(int i=1; i<=n; i++)
		cin>>px[i]>>py[i];
	for(int i=1; i<n; i++)
		for(int j=i+1; j<=n; j++)
			p[l++]= {i,j,sqrt((px[i]-px[j])*(px[i]-px[j])+(py[i]-py[j])*(py[i]-py[j]))};
	sort(p,p+l);
	double sum=0;
	int b=0;
	for(int i=0; i<l; i++)
	{
		if(b>=n-1-m+1)
			break;
		int n1=find(p[i].x);
		int n2=find(p[i].y);
		if(n1!=n2)
		{
			sum=p[i].s;
			b++;
			a[n1]=n2;
		}
	}
	printf("%.2lf",sum);
}

#10066. 「一本通 3.1 练习 1」新的开始

在这里插入图片描述
代码

#include "stdio.h"
#include "string.h"
#include "algorithm"
using namespace std;
int a[999999+10],n;
struct ppp
{
	int a,b,l;
}p[999999];
void init()
{
	for(int i=0; i<=n; i++)
		a[i]=i;
}
bool cmp(ppp z,ppp w)
{
	return z.l<w.l;
}
int find(int z)
{
	if(a[z]==z) return z;
	return a[z]=find(a[z]);
}
int main()
{
	int m,j,k=0,i,s=0;
	scanf("%d",&n);
	init();
	for(i=1; i<=n; i++)
	{
		scanf("%d",&m);
		p[k].a=0,p[k].b=i,p[k++].l=m;//很巧妙,建立发电站点与0相连 
	}//这道题意思是每个点只要有电就行,单看发电站我们可以知道有两个及以上的发电站用并查集写的话
	//你有几个发电站,就会有几个首领(几个集合,不相交),而用了0后就很巧妙,无论你有几个发电站,
	//最后都是一首领(一个集合) 
	for(i=1; i<=n; i++)
	{
		for(j=1; j<=n; j++)
		{
			scanf("%d",&m);
			p[k].a=i,p[k].b=j,p[k++].l=m;
		}
	}
	sort(p,p+k,cmp);
	int h=0;
	for(i=0; i<k; i++)
	{
		if(h==n)
			break;
		int x=find(p[i].a);
		int y=find(p[i].b);
		if(x!=y)
		{
			a[x]=y;
			h++;
			s+=p[i].l;
		}
	}
	printf("%d\n",s);
}

#10067. 「一本通 3.1 练习 2」构造完全图

在这里插入图片描述
代码

#include "stdio.h"
#include "algorithm"
using namespace std;
#define ll long long
struct ppp
{
	int u,v;
	ll w;
	bool operator <(const ppp &z)const
	{
		return w<z.w;
	}
} p[100099];
int a[100099];
int dian[100099];
int find(int x)
{
	if(a[x]==x) return x;
	return a[x]=find(a[x]);
}
int main()
{
	int n;
	ll ans=0;
	scanf("%d",&n);
	for(int i=0; i<n-1; i++)
	{
		scanf("%d%d%lld",&p[i].u,&p[i].v,&p[i].w);
		ans+=p[i].w;
	}
	sort(p,p+n-1);
	for(int i=1; i<=n; i++)
	{
		a[i]=i;
		dian[i]=1;
	}
	for(int i=0; i<n-1; i++)
	{
		int f1=find(p[i].u);
		int f2=find(p[i].v);
//你扩展的边不改变原本的最小生成树的权值,又因为有且仅有一个,故p[i].w+1
		ans+=(dian[f1]*dian[f2]-1)*(p[i].w+1);//dian[f1]*dian[f2]-1:减一是因为ans中已经加了p[i].u到p[i].v权值
		dian[f1]+=dian[f2];//在这个集合中,同一个祖先有多少个点
		a[f2]=f1;
	}
	printf("%lld\n",ans);
}

#10068. 「一本通 3.1 练习 3」秘密的牛奶运输

在这里插入图片描述
代码

#include "stdio.h"
#include "string.h"
#include "algorithm"
using namespace std;
int l=0;
int link[2999],a[509];
int ma[509][509],mi[509][509];
struct edge
{
	int u,v,w;
	int b;
	bool operator<(const edge &e)const
	{
		return w<e.w;
	}
} ed[10009];
struct tree
{
	int v,w,next;
} t[1099];
void creat(int u,int v,int w)
{
	t[l].v=v;
	t[l].w=w;
	t[l].next=link[u];
	link[u]=l++;
}
void add(int k,int v,int u,int mmax,int mmin)
{
	//k是开始的地方,像根节点·
	ma[k][v]=mmax,mi[k][v]=mmin;
//ma[k][v]求的是点k到点v走过的边中哪条边最大 
//mi[k][v]求的是点k到点v走过的边中哪条边第二大
	for(int j=link[v];j!=-1; j=t[j].next)
	{
		int vv=t[j].v;
		int ww=t[j].w;
		if(vv!=u)
		{
//注意这儿要设置变量,mmax和mmin的值不能变,
//后面返回到这一步时,mmax和mmin要的是原值 
			int t1=mmax,t2=mmin;
			if(ww>mmax)
				t2=mmax,t1=ww;
			else if(ww>mmin)
				t2=ww;
			add(k,vv,v,t1,t2);
		}
	}
}
int find(int x)
{
	if(a[x]==0) a[x]=x;
	if(a[x]==x) return x;
	return a[x]=find(a[x]);
}
int main()
{
	int n,m,p=0;
	scanf("%d%d",&n,&m);
	long long sum=0,ans=1e18;
	memset(link,-1,sizeof(link));
	for(int i=0; i<m; i++)
	{
		ed[i].b=0;
		scanf("%d%d%d",&ed[i].u,&ed[i].v,&ed[i].w);
	}
	sort(ed,ed+m);
	for(int i=0; i<m; i++)
	{
		if(p==n-1)
			break;
		int n1=find(ed[i].u);
		int n2=find(ed[i].v);
		if(n1!=n2)
		{
			p++;
			a[n1]=n2;
			sum+=ed[i].w;
			ed[i].b=1;
			creat(ed[i].u,ed[i].v,ed[i].w);
			creat(ed[i].v,ed[i].u,ed[i].w);
		}
	}
	//通过树 求出i到其他点的路径中的最大边
	for(int i=1; i<=n; i++)
		add(i,i,-1,0,0);
	for(int i=0; i<m; i++)
	{
		if(!ed[i].b)
		{
			int u=ed[i].u,v=ed[i].v,w=ed[i].w;
//如果这条边比u到v的路径中的最大边还大 那么可以替换 不然替换了反而变小 
			if(w>ma[u][v])
				ans=min(ans,sum-ma[u][v]+w);
//如果和最大的边相等 那么判断是否大于次大边 
			else if(w>mi[u][v])
				ans=min(ans,sum-mi[u][v]+w);
		}
	}
	printf("%lld\n",ans);
}

#10069. 「一本通 3.1 练习 4」Tree

在这里插入图片描述
代码

//如果我们把白色的边增大,
//那么这棵最小生成树里面的白色边就会少一些
//(因为边越大,排序的次序就越靠后),反之亦然。
//那么我们跑一个二分,把所有白边减一个mid,
//如果在此时最小生成树的白边数量比题目要求大,
//就说明目前mid的值大了,将其减少,否则将其增大。
//刚好题目也给了,边权值的范围:0 ~100
#include "stdio.h"
#include "algorithm"
using namespace std;
#include "string.h"
int ans;
int d,b,need;
struct ppp
{
	int u,v,w,co;
	bool operator<(const ppp &z)const
	{
		if(w!=z.w)
		return w<z.w;
		return co<z.co;
	}
}p[100009];
int a[50009];
int find(int x)
{
	if(a[x]==-1) a[x]=x;
	if(a[x]==x) return x;
	return a[x]=find(a[x]);
}
int ppp(int m)
{
	for(int i=0;i<b;i++)//改变白边的值 
	{
		if(p[i].co==0)
			p[i].w+=m;
	}
	sort(p,p+b);
	memset(a,-1,sizeof(a));
	int s=0;
	int k=0;//记录白边数 
	ans=0;//最先生成树权值 
	for(int i=0;i<b;i++)
	{
		if(s>=d-1)
			break;
		int n1=find(p[i].u);
		int n2=find(p[i].v);
		if(n1!=n2)
		{
			a[n1]=n2;
			s++;
			if(p[i].co==0)
				k++;
			ans+=p[i].w;
		}
	}
	for(int i=0;i<b;i++)//把白边变回原来的值 
	{
		if(p[i].co==0)
			p[i].w-=m;
	}
	return k;
}
int main()
{
	scanf("%d%d%d",&d,&b,&need);
	for(int i=0;i<b;i++)
		scanf("%d%d%d%d",&p[i].u,&p[i].v,&p[i].w,&p[i].co);
	int r=0;
	int k=-100;
	int j=100;
	int mi,s;
	while(k<=j)//二分 
	{
		mi=(int)(k+j)/2;
		s=ppp(mi);
		if(s>=need)
			k=mi+1,r=mi;
		else 
			j=mi-1;
// if(s>=need)为什么>和=可以写在一起了,在二分中>在像=靠齐,他的边界为=
//  在二分中而<也在向=靠齐,他的边界也为=,可不可以把=和<放在一起 
//注意:如果这样写就不对
//		if(s>need)
//			k=mi+1;
//		else 
//			j=mi-1,r=mi;
//应为他要的的是最小权,当s==need时,我们要判断是否有更小的mi,满足s==need;
//正确的是mi在减小,判断是否有更小的mi,满足s==need;
//错误的是mi在增加 ,判断是否有更大的mi,满足s==need; 
	}
	s=ppp(r);
	printf("%d\n",ans-need*r);//最后别忘了白边值变回来 
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值