9.19 复习写题报告(生成树)
9.19 做题报告
上午主要复习了一下图论中比较基础的最小生成树,打了几道水题(大雾)。
1.P1195 口袋的天空
模板题,只要联通块数变为 \(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 无线通讯网
也算是一道比较裸的题。
我们其实求的是当联通块数为 \(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 公路修建
这题属实把我绕蒙了,绕来绕去就是求个最小生成树的板子题。
然后自信的打了个 \(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 兽径管理
今天上午做的题里面唯一一道比较有思维含量的题。
首先,我们的暴力做法就是对每个都重新跑一边最小生成树,那样肯定会 \(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;
}