1. 程式人生 > 實用技巧 >9.19 寫題報告

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;
}