P1111 修复公路
题目背景
A地区在地震过后,连接所有村庄的公路都造成了损坏而无法通车。政府派人修复这些公路。
题目描述
给出A地区的村庄数N,和公路数M,公路是双向的。并告诉你每条公路的连着哪两个村庄,并告诉你什么时候能修完这条公路。问最早什么时候任意两个村庄能够通车,即最早什么时候任意两条村庄都存在至少一条修复完成的道路(可以由多条公路连成一条道路)
输入格式
第11行两个正整数N,M
下面M行,每行3个正整数x,y,t,告诉你这条公路连着x,y两个村庄,在时间t时能修复完成这条公路。
输出格式
如果全部公路修复完毕仍然存在两个村庄无法通车,则输出−1,否则输出最早什么时候任意两个村庄能够通车。
输入输出样例
输入 #1
4 4
1 2 6
1 3 4
1 4 5
4 2 3
输出 #1
5
说明/提示
N≤1000,M≤100000
100000x≤N,y≤N,t≤100000
试了好几次都时间超限的错误代码
#include<stdio.h>
int fa[1010];
void init(int n)
{
for(int i=1;i<=n;++i)
fa[i]=i;
}//初始化
int find(int x)
{
if(x==fa[x])
return x;
else
{
fa[x]=find(fa[x]);
return fa[x];
}
}//查询
struct time
{
int x;
int y;
int t;
}num[1000000],temp;
int max(int x,int y)
{
return (x>y)?x:y;
}
int main()
{
int N,M,maxx=-1,tx,ty,i,j;
scanf("%d %d",&N,&M);
init(N);
for(i=0;i<M;i++)
scanf("%d %d %d",&num[i].x,&num[i].y,&num[i].t);
for(i=0;i<M-1;i++)
for(j=M-1;j>i;j--)
if(num[i].t>num[j].t)
{
temp=num[i];
num[i]=num[j];
num[j]=temp;
}
for(i=0;i<M;i++)
{
if(N==1)
{printf("%d",maxx);break;}
tx=find(num[i].x);
ty=find(num[i].y);
if(tx!=ty)
{
fa[tx]=ty;
maxx=max(maxx,num[i].t);
N--;
}
}
if(N!=1)
printf("-1");
}
然后去补了一下最小生成树。
Kruskal算法
克鲁斯卡尔算法(Kruskal)是一种使用贪婪方法的最小生成树算法。 该算法初始将图视为森林,图中的每一个顶点视为一棵单独的树。 一棵树只与它的邻接顶点中权值最小且不违反最小生成树属性(不构成环)的树之间建立连边。
来自:
图解:什么是最小生成树? - 程序员景禹的文章 - 知乎https://zhuanlan.zhihu.com/p/136387766
代码如下:
int Find(int *parent, int f)
{
while( parent[f] > 0 )
{
f = parent[f];
}
return f;
}
// Kruskal算法生成最小生成树
void MiniSpanTree_Kruskal(MGraph G)
{
int i, n, m;
Edge edges[MAGEDGE]; // 定义边集数组
int parent[MAXVEX]; // 定义parent数组用来判断边与边是否形成环路
int eCount = 0;
for( i=0; i < G.numVertexes; i++ )
{
parent[i] = 0;
}
for( i=0; i < G.numEdges; i++ )
{
n = Find(parent, edges[i].begin); // 4 2 0 1 5 3 8 6 6 6 7
m = Find(parent, edges[i].end); // 7 8 1 5 8 7 6 6 6 7 7
if( n != m ) // 如果n==m,则形成环路,不满足!
{
parent[n] = m; // 将此边的结尾顶点放入下标为起点的parent数组中,表示此顶点已经在生成树集合中
printf("(%d, %d) %d ", edges[i].begin, edges[i].end, edges[i].weight);
++eCount;
if( eCount == (G.numVertexes-1)){
break;
}
}
}
}
但是我只是大概懂了这个算法的过程是怎么样的,对于怎么实现这个算法还是不懂。照着写了几次答案还是错误的😭
P1455 搭配购买
题目描述
明天就是母亲节了,电脑组的小朋友们在忙碌的课业之余挖空心思想着该送什么礼物来表达自己的心意呢?听说在某个网站上有卖云朵的,小朋友们决定一同前往去看看这种神奇的商品,这个店里有 nn 朵云,云朵已经被老板编号为 1,2,3,...,n并且每朵云都有一个价值,但是商店的老板是个很奇怪的人,他会告诉你一些云朵要搭配起来买才卖,也就是说买一朵云则与这朵云有搭配的云都要买,电脑组的你觉得这礼物实在是太新奇了,但是你的钱是有限的,所以你肯定是想用现有的钱买到尽量多价值的云。
输入格式
第一行输入三个整数n,m,w,表示有 n 朵云,m个搭配和你现有的钱的数目。
第二行至 n+1行,每行有两个整数,ci,di,表示第 i朵云的价钱和价值。
第 n+2 至 n+1+m 行 ,每行有两个整数ui,vi。表示买第 ui 朵云就必须买第 vi 朵云,同理,如果买第 vi 朵就必须买第ui 朵。
输出格式
一行,表示可以获得的最大价值。
输入输出样例
输入 #1
5 3 10
3 10
3 10
3 10
5 100
10 1
1 3
3 2
4 2
输出 #1
1
说明/提示
对于 30% 的数据,满足1≤n≤100;
对于 50% 的数据,满足 1≤n,w≤10^3,1≤m≤100;
对于 100% 的数据,满足 1≤n,w≤10^4,0≤m≤5×10^3。
#include<stdio.h>
int n,m,w;
int fa[10005],f[10005];
struct cloud
{
int c;//价钱
int d;//价值
}num[10005];
int find(int x)
{
if(x==fa[x])
return x;
else
{
fa[x]=find(fa[x]);
return fa[x];
}
}//查询
void merge(int i,int j)
{
int fx=find(i),fy=find(j);
if(fx!=fy)
fa[fx]=fy;
}//合并
int max(int x,int y)
{
return (x>y)?x:y;
}
int main()
{
int i,j,k;
scanf("%d %d %d",&n,&m,&w);
for(i=1;i<=n;i++)
{
scanf("%d %d",&num[i].c,&num[i].d);
fa[i]=i;
}
for(i=1;i<=m;i++)
{
int a,b;
scanf("%d %d",&a,&b);
merge(a,b);
}
for(i=1;i<=n;i++)
{
if(fa[i]!=i)
{
int ffa=find(i);
num[ffa].c+=num[i].c;
num[ffa].d+=num[i].d;//把价格和价值全加给根结点
num[i].c=0;num[i].d=0;//子结点就赋值为0
}
}
for(i=1;i<=n;i++)
{
if(fa[i]==i)
{
int p=num[i].c;
for(j=w;j>=p;j--)
f[j]=max(f[j],f[j-p]+num[i].d);
}
}
printf("%d",f[w]);
}
这个再写背包的时候搞了好久,我想把子结点的价格和价值都加给根结点,再把根结点拿出来排序求解。就这样:
int ci[10005],di[10005],a=1,b=1,e,s=0,s1=0;
for(i=1;i<=n;i++)
{
if(num[i].c!=0)
{
ci[a++]=num[i].c;
di[b++]=num[i].d;
}
}
for(i=1;i<a;i++)
for(j=1;j<a-1-i;j++)
if(ci[j]>ci[j+1])
{
e=ci[j];ci[j]=ci[j+1];ci[j+1]=e;
e=di[j];di[j]=di[j+1];di[j+1]=e;
}
for(i=1;i<a;i++)
{
if(s+ci[i]<w)
{
s+=ci[i];
s1+=di[i]
}
else
break;
}
printf("%d",s1);
然后又时间超限了。参照了题解才知道背包可以这样简便的写:
for(i=1;i<=n;i++)
{
if(fa[i]==i)
{
int p=num[i].c;
for(j=w;j>=p;j--)
f[j]=max(f[j],f[j-p]+num[i].d);
}
}
求出最大值最后输出f[w]就可以了。但是还是搞不懂为什么要写f[j]=max(f[j],f[j-p]+num[i].d)就可以求出最大值也不需要排序😩。