P2121 拆地毯
题目背景
还记得 NOIP 2011 提高组 Day1 中的铺地毯吗?时光飞逝,光阴荏苒,三年过去了。组织者精心准备的颁奖典礼早已结束,留下的则是被人们踩过的地毯。请你来解决类似于铺地毯的另一个问题。
题目描述
会场上有 n 个关键区域,不同的关键区域由 m 条无向地毯彼此连接。每条地毯可由三个整数 u、v、w 表示,其中 u 和 v 为地毯连接的两个关键区域编号,w 为这条地毯的美丽度。
由于颁奖典礼已经结束,铺过的地毯不得不拆除。为了贯彻勤俭节约的原则,组织者被要求只能保留 K 条地毯,且保留的地毯构成的图中,任意可互相到达的两点间只能有一种方式互相到达。换言之,组织者要求新图中不能有环。现在组织者求助你,想请你帮忙算出这 K 条地毯的美丽度之和最大为多少。
输入格式
第一行包含三个正整数 n、m、K。
接下来 m 行中每行包含三个正整数 u、v、w。
输出格式
只包含一个正整数,表示这 K 条地毯的美丽度之和的最大值。
输入输出样例
输入 #1
5 4 3
1 2 10
1 3 9
2 3 7
4 5 3
输出 #1
22
说明/提示
选择第 1、2、4 条地毯,美丽度之和为 10 + 9 + 3 = 22。
若选择第 1、2、3 条地毯,虽然美丽度之和可以达到 10 + 9 + 7 = 26,但这将导致关键区域 1、2、3 构成一个环,这是题目中不允许的。
1<=n,m,k<=100000
思路
和之前两个题目差不多的解决方法,只是它是求最大的权重值
代码如下:
#include<stdio.h>
int n,m,k;
int f[100005]={0},sum=0,count=0;
struct edge
{
int u;
int v;
int w;
}e[100005];
void qsort(int left,int right)
{
int i,j;
struct edge t;
if(left>right)
return ;
i=left;j=right;
while(i!=j)
{
while(e[j].w>=e[left].w&&i<j)
j--;
while(e[i].w<=e[left].w&&i<j)
i++;
if(i<j)
{
t=e[i];
e[i]=e[j];
e[j]=t;
}
}
t=e[left];
e[left]=e[i];
e[i]=t;
qsort(left,i-1);
qsort(i+1,right);
return ;
}//快排
int getf(int v)
{
if(f[v]==v)
return v;
else
{
f[v]=getf(f[v]);
return f[v];
}
}//并查集查找
int merge(int v,int u)
{
int t1,t2;
t1=getf(v);
t2=getf(u);
if(t1!=t2)
{
f[t2]=t1;
return 1;
}
return 0;
}//并查集合并
int main()
{
scanf("%d %d %d",&n,&m,&k);
for(int i=1;i<=m;i++)
scanf("%d %d %d",&e[i].u,&e[i].v,&e[i].w);
qsort(1,m);
for(int i=1;i<=n;i++)
f[i]=i;
for(int i=m;i>=1;i--)
{
if(merge(e[i].u,e[i].v))
{
count++;
sum+=e[i].w;
}
if(count==k)
break;
}
printf("%d",sum);
}
最小生成树
Prim算法
//最小生成树实现Prim算法
#include<stdio.h>
int main(){
int i,j,n,m,k,min,t1,t2,t3;
int e[7][7],dis[7],book[7]={0};//这里对book数组进行初始化
int inf=99999999;//存储一个我们认为的正无穷值
int count=0,sum=0;// count 用来记录生成树中顶点的个数,sum用来存储路径之和
//读入n和m,n表示顶点数目,m表示边的数目
scanf("%d %d",&n,&m);
//初始化
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(i==j) e[i][j]=0;
else e[i][j]=inf;
//读入边
for(i=1;i<=m;i++)
{
scanf("%d %d %d",&t1,&t2,&t3);
//注意这里是无向图,所以需要将边反向再存储一遍
e[t1][t2]=t3;
e[t2][t1]=t3;
}
//初始化dis数组,这里是1号顶点到各个顶点的初始距离,因为当前生成树中只有1号顶点
for(i=1;i<=n;i++)
{
dis[i]=e[1][i];
}
//Prim算法核心代码
//将1号顶点加入生成树
book[1]=1; //用book来标记一个点是否已经加入了生成树
count++;
while(count<n)
{
min=inf;
for(i=1;i<=n;i++)
{
if(book[i]==0 && dis[i]<min)
min=dis[i];
j=i;
}
book[j]=1;count++;sum=sum+dis[j];
//扫描当前顶点j所有的边,再次以j为中间点,更新生成树到每一个非树顶点的距离
for(k=1;k<=n;k++)
{
if(book[k]==0 &&dis[k]>e[j][k])
dis[k]=e[j][k];
}
}
printf("%d ",sum);
getchar();
getchar();
return 0;
}
堆优化后的Prim算法
/*prim算法实现:使用邻接表来存储图并且使用堆优化*/
#include<stdio.h>
#define inf 999999;
int dis[7], book[7];// book数组用来记录哪些顶点已经放入生成树中
int h[7], pos[7]; //h用来保存堆,pos用来保存堆中每一个顶点的位置
int size; //size用来表示堆的大小
void swap(int x,int y)
{
int t = h[x];
h[x] = h[y];
h[y] = t;
//同步更新pos
t = pos[h[x]];
pos[h[x]] = pos[h[y]];
pos[h[y]] = t;
return ;
}
//向下调整函数
void siftdown(int i) //对堆中编号为i的节点实时向下调整
{
int t, flag = 0; //flag用来标记是否需要继续向下调整
while (i*2<= size&&flag==0) //左孩子存在
{
if (dis[h[i]] > dis[h[2 * i]])
t = i * 2;
else
t = i;
//如果有右儿子急需判断
if (i * 2 + 1 <= size)
{
if (dis[h[t]] > dis[i * 2 + 1])
t = i * 2 + 1;
}
if (t != i)
{
swap(t, i);
i = t; //便于接下来继续向下调整
}
else
flag = 1;//不在需要向下调整了
}
return ;
}
void siftup(int i) //对编号i进行向上调整
{
int flag = 0;
if (i == 1)
return;//在堆顶直接返回
//不在堆顶并
while (i != 1&&flag==0)
{
//与父节点进行比较
if (dis[h[i / 2]] > dis[h[i]])
swap(i, i / 2);
else
flag = 1;
i = i / 2; //更新节点方便下一次使用
}
return ;
}
void create() //创建一个堆
{
//从最后一个非叶节点开始实行向下调整
for (int i = h_size / 2; i >= 1; --i)
siftdown(i);
}
//从堆顶中取出一个元素
int pop()
{
int t = h[1];
pos[t] = 0;
h[1] = h[size];
pos[h[1]] = 1; //更新顶点h[1]在堆中的位置
size--;
siftdown(1); //向下调整
return t;
}
int main()
{
int n, m;//顶点个数和边的个数
int u[19], v[19], w[19]; //采用邻接矩阵来存储图 表示顶点u[i]到顶点v[i]的权重为w[i] 由于为无向图实际的大小为2*m+1
int first[7]; //存储的是节点i的第一条边的编号为first[i],大小为n+1;
int next[19]; //存储的是编号为i的边的下一条边的编号next[i]。
scanf("%d %d",&n,&m);
for (int i = 1; i <= m; ++i)
{
scanf("%d %d %d",&u[i],&v[i],&w[i]);
}
//由于为无向图所以还需要存储一遍
for (int i = m + 1; i <= 2 * m; ++i)
{
u[i] = v[i - m];
v[i] = u[i - m];
w[i] = w[i - m];
}
//采用邻接表来存储图,首先对first数组初始化,最开始没有读入边 所以记录为-1;
for (int i = 1; i <= n; ++i)
first[i] = -1;
for (int i = 1; i <= 2 * m; ++i)
{
next[i] = first[u[i]];
first[u[i]] = i;
}
//prim算法核心
int count = 0;
int sum = 0;
//1号顶点加入到生成树中
book[1] = 1;
count++;
//初始化dis数组
dis[1] = 0;
for (int i = 2; i <= n; ++i)
dis[i] = inf;
int k = first[1]; //1号节点的第一条边的编号
while (k!=-1)
{
dis[v[k]] = w[k];
k = next[k];
}
//初始化堆
size = n;
for (int i = 1; i <= h_size; ++i)
{
h[i] = i;
pos[i] = i;
}
create();
pop();//先弹出堆顶元素 此时堆顶元素是一号顶点
while (count<n)
{
//堆顶元素加入到生成树当中
int j = pop();
book[j] = 1;
count++;
sum += dis[j];
//以j为中心对边进行松弛
int k = first[j];
while (k!=-1)
{
if (book[v[k]] == 0 && dis[v[k]] > w[k])
{
dis[v[k]] = w[k]; //更新距离
siftup(pos[v[k]]); //对该顶点在堆中的位置进行松弛,pos[i]中存放的是节点i在堆中的位置
}
k = next[k];
}
}
printf("%d",sum);
getchar();getchar();
return 0;
}
P2872 [USACO07DEC]Building Roads S
题目描述
Farmer John had just acquired several new farms! He wants to connect the farms with roads so that he can travel from any farm to any other farm via a sequence of roads; roads already connect some of the farms.
Each of the N (1 ≤ N ≤ 1,000) farms (conveniently numbered 1..N) is represented by a position (Xi, Yi) on the plane (0 ≤ Xi ≤ 1,000,000; 0 ≤ Yi ≤ 1,000,000). Given the preexisting M roads (1 ≤ M ≤ 1,000) as pairs of connected farms, help Farmer John determine the smallest length of additional roads he must build to connect all his farms.
给定 n 个点的坐标,第 i 个点的坐标为 (xi,yi),这 n 个点编号为 11 到 n。给定 m 条边,第 i 条边连接第 ui 个点和第 vi 个点。现在要求你添加一些边,并且能使得任意一点都可以连通其他所有点。求添加的边的总长度的最小值。
输入格式
* Line 1: Two space-separated integers: N and M
* Lines 2..N+1: Two space-separated integers: Xi and Yi
* Lines N+2..N+M+2: Two space-separated integers: i and j, indicating that there is already a road connecting the farm i and farm j.
第一行两个整数 n,m 代表点数与边数。
接下来 n 行每行两个整数xi,yi 代表第 i 个点的坐标。
接下来 m 行每行两个整数ui,vi 代表第 i 条边连接第 ui 个点和第vi 个点。
输出格式
* Line 1: Smallest length of additional roads required to connect all farms, printed without rounding to two decimal places. Be sure to calculate distances as 64-bit floating point numbers.
一行一个实数代表添加的边的最小长度,要求保留两位小数,为了避免误差, 请用 6464 位实型变量进行计算。
输入输出样例
输入 #1
4 1
1 1
3 1
2 3
4 3
1 4
输出 #1
4.00
说明/提示
数据规模与约定
对于 100%100% 的整数,1≤n,m≤1000,1≤xi,yi≤10^6,1≤ui,vi≤n。
说明
Translated by 一只书虫仔。
思路
这个和上次哪个哨所的差不多,但是这场是指定好的哪些点之间已经有路了。不知道为什么题目给定的坐标明明是整形,需要改成double才能过,最好把所有长度相关的数据都改成double,不然就会一直wa😰
代码如下
#include<stdio.h>
#include<math.h>
int k=1;
double x[1005],y[1005];
int f[1005],count=0;
double sum=0;
struct edge
{
int u;
int v;
double dis;
}e[2023010];
double coor(int i,int j)
{
double z;
z=(x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);
return z;
}//求点之间距离的平方
void qsort(int left,int right)
{
int i,j;
struct edge t;
if(left>right)
return ;
i=left;j=right;
while(i!=j)
{
while(e[j].dis>=e[left].dis&&i<j)
j--;
while(e[i].dis<=e[left].dis&&i<j)
i++;
if(i<j)
{
t=e[i];
e[i]=e[j];
e[j]=t;
}
}
t=e[left];
e[left]=e[i];
e[i]=t;
qsort(left,i-1);
qsort(i+1,right);
return ;
}//快排
int getf(int v)
{
if(f[v]==v)
return v;
else
{
f[v]=getf(f[v]);
return f[v];
}
}//并查集查找
int merge(int v,int u)
{
int t1,t2;
t1=getf(v);
t2=getf(u);
if(t1!=t2)
{
f[t2]=t1;
return 1;
}
return 0;
}//并查集合并
int main()
{
int n,m,a,b;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%lf %lf",&x[i],&y[i]);
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
{
e[k].u=i;
e[k].v=j;
e[k].dis=coor(i,j);
k++;
}
for(int i=1;i<=m;i++)
{
scanf("%d %d",&a,&b);
e[k].u=a;
e[k].v=b;
e[k].dis=0;
k++;
}
qsort(1,k);
for(int i=1;i<=n;i++)
f[i]=i;
for(int i=1;i<=k;i++)
{
if(merge(e[i].u,e[i].v))
{
count++;
sum+=sqrt(e[i].dis);
}
if(count==n-1)
break;
}
printf("%.2lf",sum);
}