64,648
社区成员
上次我们先简单学习了单源最短路问题,也就是说起点是固定的。
尤其是spfa和dijkstra,必须熟记模板!!
我之前发过一篇解析,关于并查集。
复习以后,我们进入正题……
这个弗洛伊德,是1978年图灵奖获得者。
没事,他先不看。你们学计算机再详细看他。
先来思想:
其实它是基于动态规划的,代码特别像多维dp。老玩家明白。
假设我们写下一组数据:dp[k][i][j],表示在经过的店不超过k个时,从i到j的最短路线。
算法也很简单,假设你现在站在一个十字路口,位置是i,你要去名字叫j的饭馆,路口处有一个叫k的超市。这样你有两种选择:经过k去买点东西,再从k走到j,和不经过k直接走。我们假设这个路口所有走法都行得通且不越界,那么我们要算的就很简单了:
假设你知道经过k去j的距离,也知道直接到j的距离,那你就可以选短一点的那条了。这个算法,上班族们每天都在用,他们却不知情……当你看地图时,你已经默默地在心中运行了Floyd。
但是!Floyd使用的场景是多源最短路,也就是你不知道该从哪里开始,只好把从所有点开始的最短路都算出来。感觉费手了是不是。
其实程序实现就四行代码,其中3个循环,所以别看他在你脑中运行的特别快,实际上时间复杂度为O(n³)。这一点是所有基于多维dp的算法的通病。
上模板代码吧,我讲累了。
void floyd()
{
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]);
}
int main()
{
cin>>n>>m>>k;
//初始化:自己到自己的点是0,其余没有走到的inf。
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i==j) dp[i][j]=0;
else dp[i][j]=inf;
int x,y,z;
while(m--)
{
cin>>x>>y>>z;
dp[x][y]=min(dp[x][y],z);
}
floyd();
我就给一段,剩下的自己写,有问题关注我,我给你调试。
并查集
首先我们来看一个例子:
A和B是朋友,B和C是朋友,C和D是朋友,A和D是不是朋友?
你应该马上就会回答:是,因为朋友的朋友是朋友,所以是朋友。
还有一个例子:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。那给你两个人,他两个是亲戚吗?
你又会马上回答:是或不是,因为它属于/不属于家谱!
你看,你的脑中又运行了一个算法:并查集。
先说集合,概括为四个字:“一堆东西”,里面的东西不重复(互异性)且都是确定的(确定性),集合内部元素没有高低之分,也就是没有顺序,相当于随便排队。
拿找亲戚说:我们按照家谱画一棵树,如果判断两个人是不是亲戚,只需要看他俩根节点是不是一样的就好了。因为根节点一样代表他俩在同一集合里,也就是亲戚了(虽然关系可能很远)。
这其实就是并查集。并查集有个优化叫路径压缩。
我们在建树的时候,可以把父亲节点连一条线出去,直接跳过中间的点。
并查集要用递归,总共分三步:
一,初始化,把所有节点独立开,后面再连起来
二,把x和y的树合并起来 。这里有人要问了,两棵树怎么合并
那么不成还要捏到一块啊,你以为树是泥做的啊?实际上我们只需要把两个点所在的树的根节点用一条线连接起来就可以啦。就好比是你想把A村和B村结合起来,你肯定不可能把他两个捏一块,只能在中间来一条路。
三,就是我刚才提到的路径压缩优化法。
路径压缩其实是把每一条边赋值到上面去,让根节点与所有节点直接相连,不光好看还好找数据。
话不多说上模板
int fin(int x)
{
if(x==f[x]) return x;
return f[x]=fin(f[x]);//路径压缩,把边复制到上面去,造成所有点与根节点直接相连
}
int main()
{
cin>>n>>m>>p;
for(int i=1;i<=n;i++)
f[i]=i;//初始化,把所有人独立开,后面再连起来
for(int i=1;i<=m;i++)
{
int x,y;
cin>>x>>y;//把x和y的树合并起来
//把两个点所在的树的根节点连起来,树就连起来了
int xx=fin(x),yy=fin(y);
f[xx]=yy;
}
while(p--)
{
int x,y;
cin>>x>>y;
int xx=fin(x),yy=fin(y);
这次的模板给的比较全,并查集函数和main函数的一部分都给出来了。
请注意剩下的自己写,别抄我代码!这只是个模板。有问题关注我,给你线上调试。
并查集代码比较多变,比如这个
#include<iostream>
using namespace std;
int n,m,pre[1000];
void init(){
for(int i = 1; i <= n;++i){
pre[i] = i;
}
}
int Find(int x){
return x == pre[x] ? x : pre[x] = Find(pre[x]);
}
void merge(int x,int y){
pre[Find(x)] = Find(y);
}
的处理方法就跟之前的不一样,把处理函数放到了主函数外面(我的代码在主函数里面进行处理)。并查集还可以加入素数判断等等,只要这个问题能用并查集解决,不管前面要算多少步,主体代码永远不变。
标红代码必须给我背过,这个在并查集应用里永远不会变!
int fin(int x)
{
if(x==f[x]) return x;
return f[x]=fin(f[x]);
}
说累了,来做一个题吧。
集合
时间限制:1秒 内存限制:128M
题目描述
现在给你一些连续的整数,它们是从A到B的整数。一开始每个整数都属于各自的集合,然后你需要进行一下的操作:
每次选择两个属于不同集合的整数,如果这两个整数拥有大于等于P的公共质因数,那么把它们所在的集合合并。
反复如上操作,直到没有可以合并的集合为止。
现在Caima想知道,最后有多少个集合。
输入描述
输入一行,三个整数A,B,P。
其中,A≤B≤100000;2≤P≤B。
输出描述
一个数,表示最终集合的个数。
样例
输入
10 20 3
输出
7
提示
并查集
有80%的数据B≤1000。
样例解释{10,20,12,15,18},{13},{14},{16},{17},{19},{11}。
解析:
没想到啊,还要讲。
首先当然是并查集函数:
int fin(int x)
{
if(x==f[x]) return x;
return f[x]=fin(f[x]);
}
最开始我们可以找出p到a之间的每一个素数,再求一个倍数,如果这个倍数和下一个倍数都在给定范围内,他两个就能连起来。
上代码:
void prime()
{
isp[1]=true;
for(int i=2;i<=b;i++)
{
for(int j=i*2;j<=b;j+=i)
{
isp[j]=true;
}
}
for(int i=cmp;i<=b;i++)
{
if(isp[i]==0) p[++cnt]=i;
}
}
int fin(int x)
{
if(x==f[x]) return x;
return f[x]=fin(f[x]);
}
int main()
cin>>a>>b>>cmp;
for(int i=a;i<=b;i++) f[i]=i;
prime();
for(int i=1;i<=cnt;i++)
{
for(int j=1;j*p[i]<=b;j++)
{
if(j*p[i]>=a&&(j+1)*p[i]<=b)
f[fin(j*p[i])]=fin((j+1)*p[i]);
}
}
我只给主要部分,剩下的自己写,有问题找我调
在做一个题吧,我累死了
无线网络
时间限制:1秒 内存限制:128M
题目描述
有n台电脑,给出这n台电脑的坐标( xi, yi )(0 <= xi,yi <= 10000),所有电脑的初始状态全部是断电的,电脑与电脑之间如果要直接相连,两台电脑必须是距离相差在最大范围d以内。(1 <= N <= 1001, 0 <= d <= 20000).
现在进行一系列操作,按要求输出。操作一共有2种:
1.O p 开启第p台电脑的电源 。
2.S p q 查询两台电脑是否能相互联系(直接、间接),若能,则输出SUCCESS,若不能,则输出FAIL。
输入描述
第一行包含两个整数N和d(1 <= N <= 1001,0 <= d <= 20000)。N是计算机的数量,从1到N,而D是两台计算机可以直接通信的最大距离。在接下来的N行中,每行包含两个整数xi,yi(0 <= xi,yi <= 10000),这是N个计算机的坐标。从第(N + 1)行到输入的末尾,有一些操作,这些操作是一个接一个地执行的。每行包含以下两种格式之一的操作:
1."O p" (1 <= p <= N),表示维修计算机p。
2."S p q" (1 <= p, q <= N),表示测试计算机p和q是否可以通信。
输出描述
对于每项测试操作,如果两台计算机可以通信,则打印“ SUCCESS”,否则,则打印“ FAIL”。
样例
输入
4 1
0 1
0 2
0 3
0 4
O 1
O 2
O 4
S 1 4
O 3
S 1 4
输出
FAIL
SUCCESS
解析:
不明显的并查集。这个题可以用BFS解决,不过代码是橙级,有点太长
用并查集可以让代码缩短一点,这样就不那么累了
首先得看两台电脑通信的最大距离,这个相当于边权值;
如果电脑状态是开机(O),打个标记。
如果下一步你要看看两台电脑是否连接,只需要看他们的根节点是不是一样的或是相连的,前提是边权不能是0,0表示维修中。
如果他们的根节点相连或者相同,那就直接“SUCCESS”。否则就“FAIL”,因为算不出来,退出吧。
标准思路:
先思考,两点之间的距离怎么算?
答案:d=
初始化,每个电脑自称集合。
遍历所有电脑,是开的而且距离<d,进行连接,这是“O”。
查询当前是否可以连接,这是“S”。
还是上伪代码:
struct node{
int x,y;
bool opened;
}node[N];
double dis(double x,double y,double xx,double yy)
{
return sqrt((xx-x)*(xx-x)+(yy-y)*(yy-y));
}
int fin(int x)
{
if(x==f[x]) return x;
return f[x]=fin(f[x]);
}
int main(){
int n;
double d;
cin>>n>>d;
for(int i=1;i<=n;i++) f[i]=i;
for(int i=1;i<=n;i++) cin>>node[i].x>>node[i].y;
//字符我们不这样读,用字符串输入
char s[2];
int x,y;
while(cin>>s>>x)
{
if(s[0]=='O')
{
node[x].opened=1;
for(int i=1;i<=n;i++)
{
if(node[i].opened==1)
{
double dw=dis(node[x].x,node[x].y,node[i].x,node[i].y);
if(dw<=d) f[fin(x)]=fin(i);
}
}
这里我只给了O指令的执行代码,S指令的执行自己写,很简单
好了,都这么晚了,我该下课了。
饿死我了,我还没吃饭啊!各位体谅一下我,不要再在星期六晚上5:30分以后找我,我要学习的。
家庭作业:
因为各位要巩固基础嘛,所以我不布置了。好好复习。