9.19 复习写题报告(生成树)

9.19 做题报告

上午主要复习了一下图论中比较基础的最小生成树,打了几道水题(大雾)。

1.P1195 口袋的天空

Link

模板题,只要联通块数变为 \(k\) 直接 \(break\) 就行。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,k,fa[10010],ans;
struct node
{
	int u,v,w;
}e[100010];
inline int read()
{
	int s = 0,w = 1; char ch;
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * w;
}
bool comp(node a,node 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()
{
	n = read(); m = read(); k = read();
	for(int i = 1; i <= m; i++)
	{
		e[i].u = read();
		e[i].v = read();
		e[i].w = read();
	}
	sort(e+1,e+m+1,comp);
	for(int i = 1; i <= n; i++) fa[i] = i;
	int num = n;	
	for(int i = 1; i <= m; i++)//krusal 板子题
	{
		int x = e[i].u;
		int y = e[i].v;
		int fx = find(x), fy = find(y);
		if(fx == fy) continue;
		fa[fx] = fy;
		ans += e[i].w;
		if(--num == k) break;
	}
	if(num > k) printf("No Answer\n");
	else printf("%d\n",ans);
	return 0;
}
2.P1991 无线通讯网

Link

也算是一道比较裸的题。

我们其实求的是当联通块数为 \(s\) 的时候,所加进去的边权的最大值。

因为,我们可以给这 \(s\) 个联通块每个都发一个卫星电话,然后联通块之间的点可以用电话线连接。

剩下的就是最小生成树的板子啦。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
int n,k,tot,fa[1010],x[1010],y[1010];
double ans;
inline int read()
{
	int s = 0,w = 1; char ch;
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * w;
}
struct node
{
	int u,v;
	double w;
}e[510*510];
int find(int x)
{
	if(fa[x] == x) return x;
	else return fa[x] = find(fa[x]);
}
double dis(int i,int j)
{
	return sqrt((x[i]-x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]));
}
bool comp(node a,node b)
{
	return a.w < b.w;
}
int main()
{
	k = read(); n = read();
	for(int i = 1; i <= n; i++)
	{
		x[i] = read();
		y[i] = read();
	}
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j <= n; j++)
		{
			e[++tot].u = i;
			e[tot].v = j;
			e[tot].w = dis(i,j);
		}
	}
	for(int i = 1; i <= n; i++) fa[i] = i;
	sort(e+1,e+tot+1,comp);
	int num = n;
	for(int i = 1; i <= tot; i++)//krusal 板子
	{
		int fx = find(e[i].u); 
		int fy = find(e[i].v);
		if(fx == fy) continue;
		fa[fx] = fy;
		if(--num == k)
		{
			ans = e[i].w;
			break;
		}
	}
	printf("%.2lf",ans);
	return 0;
}
3.P1265 公路修建

Link

这题属实把我绕蒙了,绕来绕去就是求个最小生成树的板子题。

然后自信的打了个 \(krusal\) 的板子结果 $TLE $, 又 \(RE\) ,看了看题解才明白,原来这题是不能用 \(krusal\) 的,因为边的数量太多,

我们数组存不下,那么只能老老实实的用 \(prime\) 了。

一个小优化就是: 我们不必先把所有的距离都求出来,那样数组开不下,我们只需要在用到他的时候再算就可以了。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const double inf = 2147483647.0;
int n,x[5010],y[5010];
double dis[5010],ans;
bool vis[5010];
inline int read()
{
	int s = 0,w = 1; char ch;
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * w;
}
double d(int i,int j)
{
	return (double) sqrt((double) (x[i]-x[j]) * (x[i] - x[j]) + (double) (y[i] - y[j]) * (y[i] - y[j]));//一定要转成double 类型的
}
int main()
{
	n = read();
	for(int i = 1; i <= n; i++)
	{
		x[i] = read();
		y[i] = read();
	}	
	for(int i = 1; i <= n; i++) dis[i] = inf;
	dis[1] = 0;
	for(int i = 1; i <= n-1; i++)
	{
		int x = 0;
		for(int j = 1; j <= n; j++)//找当前距离最小的点
		{
			if(!vis[j] && (x == 0 || dis[x] >= dis[j])) x = j;
		}
		vis[x] = 1;
		for(int j = 1; j <= n; j++) //更新一下其他点的距离
		{
			if(!vis[j])dis[j] = min(dis[j], d(x,j));
		}
	}
	for(int i = 1; i <= n; i++) ans += dis[i];
	printf("%.2lf",ans);
	return 0;
}     
4.P1340 兽径管理

Link

今天上午做的题里面唯一一道比较有思维含量的题。

首先,我们的暴力做法就是对每个都重新跑一边最小生成树,那样肯定会 \(T\) 飞的。

我们其实并不需要对每次都跑一遍生成树,当我们这次要删的边不在最小生成树里面的话,那么删了这条边对答案也是没有影响的。

因此,我们可以倒着从后往前处理,对在生成树上的边标记一下,如果删的这条边在生成树上,就暴力重新跑一边生成树。

反之答案不变。

一个可以优化的地方,就是当 \(m < n-1\) 的时候,我们直接输出 \(-1\) 就可以,因为这几条边是构不成一棵树的。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1e5+10;
int n,m,tmp;
int fa[N],ans[N];
struct node
{
	int u,v,w;
	int tag,vis,id;
}e[N];
inline int read()
{
	int s = 0,w = 1; char ch;
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * w;
}
bool comp(node a,node b)
{
	return a.w < b.w;
}
int find(int x)
{
	if(fa[x] == x) return x;
	else return fa[x] = find(fa[x]);
}
void krusal()
{
	tmp = 0;
	sort(e+1,e+m+1,comp);
	for(int i = 1; i <= n; i++) fa[i] = i;
	int num = n;
	for(int i = 1; i <= m; i++) e[i].vis = 0;
	for(int i = 1; i <= m; i++)
	{
		int fx = find(e[i].u), fy = find(e[i].v);
		if(fx == fy || e[i].tag == 1) continue;
		e[i].vis = 1;
		fa[fx] = fy;
		tmp += e[i].w;
		if(--num == 1) return; 
	}
	if(num != 1) tmp = -1;
}
int main()
{
	n = read(); m = read();
	for(int i = 1; i <= m; i++)
	{
		e[i].u = read();
		e[i].v = read();
		e[i].w = read();
		e[i].id = i;
	}
	krusal(); ans[m] = tmp;
	for(int i = m-1; i >= 1; i--)
	{
		if(i < n-1)//少于 n-1 条边构不成一棵树
		{
			ans[i] = -1;
			continue;
		}
		for(int j = 1; j <= m; j++)
		{
			if(e[j].id == i+1)//找到要删除的那一条边
			{
				e[j].tag = 1;
				if(e[j].vis == 1) krusal();//如果这条边在最小生成树上,就需要在重新跑一边最小生成树
				break;
			}
		}
		ans[i] = tmp;
	}
	for(int i = 1; i <= m; i++) printf("%d\n",ans[i]);
	return 0;
}
posted @ 2020-09-19 11:31  genshy  阅读(163)  评论(0编辑  收藏  举报