Fork me on GitHub

POJ2387-Til the Cows Come Home

开始刷邝斌最短路专题

可用平台 (评分是真的不准)

poj2387 官网又上不去了

题目有点多,保证AC人数从高到低刷个8道就行了

用搜索来弄的话,

遍历T找1,

再遍历T找2,如果有T个2,T组数据都压入队列

遍历这T组数据找3

...

总共需要遍历N次,最后找出min的路

 

复杂度是T^N,(2*10^3)^(10^3),学习(回顾)新算法,这博客(有迪杰斯特拉不能处理负边的例子)说错误很多,但acwing?先放着吧

回顾图的存储

之前在HIT活动中心特意写过迪杰斯特拉的题解,忘了也不想看,现学其他人的

先了解图的存储(没看后面的以图存BFS/DFS),进而看迪杰斯特拉,但感觉代码思路怎么是先把所有点都赋值一遍?而且最开始有个 dist[t] t是-1?写博客代码这么不用心吗?而且也没图,不好理解,

又看了一篇博客,有点了解了但很朦胧,没C++代码,而且他的例子真不恰当,把贪心牵扯进的话,那苹果和吃自助的例子都需要考虑后面的情况对当前造成的影响,又有个词“无后效性”,动归,此时脑瓜子又库库冒出一堆疑问(比如比搜索好在哪),还没回顾到那些,先搁置。不管咋说这个迪杰斯特拉也是仅仅保证当前最佳吧,扯远了,感觉不太对头,先看其他的。

参考博客,虽然没C++代码,但图解足够清晰!

 

哎,回顾个这么简答的算法,脑子锈住了,回顾了一下午还没理解透彻,邝斌的鼓励“人一我十,人十我百”,菜逼就要多锤炼

 

发现每篇博客都有个很重要却千篇一律的描述 "距离最短的顶点k",又找了几篇

发现了这个博客更加了解了一些(虽然我很菜但他文章最后一句“照着打一遍”,其实只是对于小小白可以这样,基本不建议照着打,时间久了只能回忆起那个人的代码风格完全记不住代码思路,不过这人的迪杰斯特拉的讲解是目前看到最清晰详细的。看完博客里的代码知道思路就开始自己写,算法原理弄清之前不要开始写,开始写之后一点都不要再看别人的代码,每一个坑踩过才行)

 

迪杰斯特拉就一个地方不懂,也是最关键的,为啥每次都是选此时局面的最短路径作为最优解的一部分,这一定是全局最优解必经之路么?根本没道理啊,就比如,为啥先加2进来,而不先把1加进来呢,0到1,权重是5,也是对1来说的最短的啊

 

我先把上面这个博客里的图拿出来捋顺下思路(把这篇文章的链接开两个页面,标签页拖出来,win+←,分屏,一个看图,一个看下面思路叙述)

如果想知道顶点0到6这个点的最短路径,其实只要你给我0到5的最短路x,和0到4的最短路y就行,我自己比较x+3和y+7即可。有点动归的意思但暂时不想牵扯那么多没回顾的知识,就他妈的说最短路。

回到上面黑色粗体的,为啥每次要选则此时路径里的最优,我看所有博客都是先把最短的2加进来,其实我觉得跟之前说的一样,什么东西都要抓重点,而不是无脑开好大数组来规避可能出现的问题从而AC掉却也不清楚开数组问题上可能出现的真正错误点,或者无脑写一堆模板,这里也是,去掉没用的外壳,做个控制变量,把最关键的抽丝剥茧点出来,做个约束,才更好让人理解

上面博客和全网的思路,用在这个图上,都是先把2加进来。但我偏偏要先把1加进来

找0到所有点的最短路径,先做到此时最优,即贪心(但只是伪最优),比如0到2和0到1的最短分别为2和5,那随便加哪个进去都行,不用非得是最短的2进,我偏偏要加1进来(即已确定0到1的权重最短为5,不会再有更优的了),因为没有比0→1能更近到达1的点了,然后0到3,0到4更新为6和11,0到2初始数据是2

那么此时局面是,

0到2最短2

0到3最短6

0到4最短11

0到1真最短5

此时按照其他博客思路是应该先加最短的,那如果到3最短就先加3吗?放屁!理论上来说如果013最短,2这个亲儿子都没加进来,证明啥?

证明0到2简称02,02比013都远,那023只会更远,023更加比013远,对吧?那2先不用掺乎进来了,3就可以去摆平其他的路线,以最小的代价。

那如果3到5是很大的数,这时候0135这条路因为3到5很大就先停这了,回头去看2,之前说02比013远,但如果没远太多,且2到5也很小,这时候025不就比0135更优秀了吗!那5这个点就会用025这条路,用来后面的决策。

现在回过头说刚才放屁那,先加进3的问题,再回顾下此时局面,0到1最短路确定是5,加了进来,0到2/3/4都是伪的,分别为2/6/11,其实3如果是此时最优,的确实应该加进来作为已确定的,之后看0到245,但为了说明算法真正思路,这里不加3,而是加2,比如0到2的距离,比013远,但也要先加2,意思是想说,把所有到3的点(入度),都算出来,再把3加进去,而不是看谁此时路径最短就加谁进去。

2加完了现在更新3和5,0到3是8但之前是6不更新,0到5没有过,更新为10

此时局面

0到1最短5已确定

0到2最短2已确定

0到4是伪最短11

0到3是伪最短6

0到5是伪最短10

此时出现的差别先说一下,其他博客都是一直取最短放进已有数据集

我是找所有指向他的点,即入度,找完才放进数据集

此时我找入度都找完的点即3,能到他的点都走完了,最小已经定了就是013为6,那3加进来,更新45,4变为7,5变为8,

此时局面

0到1最短是5已确定

0到2最短是2已确定

0到4是伪最短7

0到3是伪最短6

0到5是伪最短8

此时按照其他博客就是最短的7,即0到4加进来,但我思路是看度,4和5入度都判断完了,我随便哪个都行,我偏偏要去加0到5进来,再更新6,之前没有过,变为11,4和5存的也因入度都判断完了变成最短

此时局面

0到1最短路是5已确定

0到2是2已确定

0到4是7伪最短

0到3是6已确定

0到5是8已确定

0到6是11伪最短

随后再加4进来,更新6,是14,比11大,不更新

0到1最短路是5已确定

0到2是2已确定

0到4是7真最短

0到3是6已确定

0到5是8已确定

0到6是11真最短

到这我发现了,答案都一样

 

我的方法最基本朴素思路最简单,是要看度,但存图只有出度没存入度。但迪杰斯特拉不考虑入度,直接看最短的就比我的思路好很多。其实原理是一样只是避开的入度,且这样能抽丝剥茧出真正的最短路思想:刷新!!每次刷新都会更新成更短的也叫松弛操作,所以每次找最近的不是必要的,找哪个点都行,之前我是这样说的,但现在发现,这里找哪个点都行,必须要是度都为0的里面,找哪个加入都行,但没法存入度就只能找最短了,这样既然省去了看入度的操作,也保证了此时点就是顶点到这都最优路径,加入数据集,再用这个点来松弛一遍他能到的点,被松弛的这些点作为此局面的伪最优,若干次伪最优之后,一次次更新一次次调教,会出现真最优。伪最优一定是往真最优方向发展的

 

跟之前反转灯泡问题 Fliptile ,还有数论素数筛一样,从最朴素暴力到最优解,有个推导过程才会理解最优解的思想

 

其实这个图把0到2权设成7更好理解,这样1点加完,找最短就加3,为什么加3?不考虑另一个入度吗?

0到1真最优5已确定

0到2是7初始值

1加入导致的更新3,013是6伪

1加入导致的更新4,014是11伪

此时确定好了1,接下来从234中考虑,我已经可以确定0到3的距离6就是最短,没必要再看2和4了,他俩等我打5的时候有难再让他俩出兵(上面有例子)。至于为啥可以确定0到3的6是最短,因为起初0到所有挨着的点都赋值了,0到2的7,再加上2到3(必须是正数,无论权是什么正数),都一定比7大,不可能更优了

 

简单来说就是,起点到某点是目前几个值里的最小值,那一定就是起点到这个点的最短路因为其他没加入的说明是无穷大,已经加入了的,那些点都还没到这呢,都比我这个大,那再经过其他路线到我这,则会更大。所以,只要找到此时最短的距离,就可以确定是顶点到这个点的最短路径,确定完一个最短路径其他所有点沾亲带故的就都更新一下,使得到顶点更近

 

尝试看代码,这些人真的刷过题吗?还是只是纸上谈兵,所有人博客都有这一句 if(!vis[j] && (node == -1 || dis[j]<dis[node])) 妈的一开始node是-1啊。艹这群傻逼。真难怪我上面说,一点没说冤枉他,我都说轻了,抄代码屁用没有,抄错都不知道。之前看过关于数组下标大佬博客,负数数组下标搁置先

 

无奈搜了个百度的迪杰斯特拉C语言代码(严蔚敏口碑极差,下面严蔚敏版本看都懒得看,一坨屎一样),真棒,百度在找题的其他平台,报错和新算法代码示例的时候很给力

 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #define max1 10000000  //原词条这里的值太大,导致溢出,后面比较大小时会出错
 4 int a[1000][1000];
 5 int d[1000];//d表示源节点到该节点的最小距离
 6 int p[1000];//p标记访问过的节点
 7 int i, j, k;
 8 int m;//m代表边数
 9 int n;//n代表点数
10 int main()
11 {
12     scanf("%d%d",&n,&m);
13     int    min1;
14     int    x,y,z;
15     for(i=1;i<=m;i++)
16     {
17         scanf("%d%d%d",&x,&y,&z);
18         a[x][y]=z;
19         a[y][x]=z;
20     }
21     for( i=1; i<=n; i++)
22         d[i]=max1;
23     d[1]=0;
24     for(i=1;i<=n;i++)
25     {
26         min1 = max1;
27         //下面这个for循环的功能类似冒泡排序,目的是找到未访问节点中d[j]值最小的那个节点,
28         //作为下一个访问节点,用k标记
29         for(j=1;j<=n;j++)
30             if(!p[j]&&d[j]<min1)
31             {
32                 min1=d[j];
33                 k=j;
34             }
35         //p[k]=d[k]; // 这是原来的代码,用下一 条代码替代。初始时,执行到这里k=1,而d[1]=0
36        //从而p[1]等于0,这样的话,上面的循环在之后的每次执行之后,k还是等于1。
37         p[k] = 1; //置1表示第k个节点已经访问过了
38         for(j=1;j<=n;j++)
39             if(a[k][j]!=0&&!p[j]&&d[j]>d[k]+a[k][j])
40                 d[j]=d[k]+a[k][j];
41     }
42     //最终输出从源节点到其他每个节点的最小距离
43     for(i=1;i<n;i++)
44         printf("%d->",d[i]);
45     printf("%d\n",d[n]); 
46     return 0;
47 }

第一次很深刻,以后估计就是这个写法了

 

回顾完迪杰斯特拉看这个题(发现两个专题第一题都跟牛有关)

这个题说是双向奶牛轨道,感觉会有这种数据吧?脚丫子想都知道

3 5 10

5 3 90

5 3 40

先处理下,处理成数据只有3 5 10再进行最短路算法

憋了半天写出的代码,简简单单直接AC

AC代码

 

 1 //N(1000)个点
 2 //T(2000)条trail
 3 //每条的权最大100
 4 
 5 #include<stdio.h>
 6 #include<iostream>
 7 #include<string.h>
 8 using namespace std;
 9 int tra[1001][1001];//tra[900][1000]=100:表示地标点900到地标点1000权重是100
10 int T,N;
11 int dir[1001];//dir[1000]表示顶点到1000这个点的最短距离
12 int vis[1001];
13 int mindir;
14 int main()
15 {
16 //    freopen("zhishu.txt","r",stdin);
17     int a,b,c;
18     //此题数据从1开始不是0开始
19     while(cin>>T>>N){
20         memset(tra,0x3f,sizeof(tra));
21         tra[1][1]=0;//第一个(1,1)点为0即可,其他的比如(3,3)没必要,因为只有第一个点需要以0距离的身份进入,后面再进入就是互相比跟其他点的距离了.至于最后一个点用没加进来只有他这个条件就可以加进去
22         for(int i=0;i<T;i++){
23             cin>>a>>b>>c;
24             if(c<tra[a][b]){
25                 tra[a][b]=c;
26                 tra[b][a]=c;//其实这样写没必要,但防止数据只有5 3 90,但需要3 5 90这种情况.理论上来说只需要小大这个顺序的轨道
27 
28             }
29         }//读取T个奶牛轨道同时进行数据的筛选
30 
31         //检测读取数据对不对
32 //        cout<<endl;
33 //        for(int i=1;i<=N;i++)
34 //            for(int j=1;j<=N;j++)
35 //                if(tra[i][j]!=0x3f3f3f3f)
36 //                    cout<<i<<" "<<j<<" "<<tra[i][j]<<endl;
37 
38         memset(dir,0x3f,sizeof(dir));
39         memset(vis,0,sizeof(vis));
40         dir[1]=0;
41         int p;
42 
43         for(int i=1;i<=N;i++){//N个点进,N次就都进来了
44 
45             mindir=0x3f3f3f3f;
46             for(int j=1;j<=N;j++){//找最小的点
47                 if(vis[j]==0 && dir[j]<mindir){
48                     mindir=dir[j];
49                     p=j;
50                 }
51             }
52             vis[p]=1;//p标记为1以后不用再更新这个点了
53 
54             //开始松弛
55             for(int k=1;k<=N;k++){
56                 if(dir[k]>dir[p]+tra[p][k])
57                     dir[k]=dir[p]+tra[p][k];
58             }
59         }
60         cout<<dir[N]<<endl;
61     }
62 }

 

看看题解博客,有没有更好的写法,学两手,结果发现好多人管这个输入叫有坑,这不读完题起手就膝跳反射,不是,条件反射么,咋会想不到

发现题解都差不多,此题over

 

备注:其实第43行 i<N 也行,再此引用那个抄人代码的家伙的博客中的一句话

“要寻找n-1次,找到点之后,对其邻接点进行松弛,为什么只要寻找n-1个点呢?因为当

剩下一个点的时候,这个点已经没有需要松弛的邻接点了。此时从源点到这个点的距离就是最短距离了。”

 

自用:

###:Adblock Plus

拦截CSDN底部关注/点赞/收藏/代码:blog.csdn.net##.left-toolbox。有时候半屏看这底部栏很挡害,但有时候又不能确定博客是不是有问题,又要取消拦截看看评论,所以拎出来记录下

拦截CSDN左边博主信息代码:blog.csdn.net##.blog_container_aside。win+←的时候就很别扭i.cnblogs.com##.left.sidebar

###:突然想到对拍是给赛场上防止WA而罚时用的,跟自己的暴力对拍

###:要专注啊!!!再也没有高中转10班后的专注时刻了

###:

1 有个总骂人被踢出去的
2 数一巨巨好友验证炼铜
3 第一届叫什么11阶梯CCCC程序设计大赛天梯赛
4 monenta计蒜客
5 毛老师:中午老师是休息的,然而我并..并不休息
6 为什么感觉poj就像没片撸一样难受
7 学吐的哈理工linlinsong阿里大佬
View Code

###:

posted @ 2024-10-06 23:22  GerJCS  阅读(4)  评论(0编辑  收藏  举报