图论 ~%?…,# *'☆&℃$︿★?入门之章

一、图的遍历与存储

1、[NOIP2015 提高组] 信息传递

题目描述

n 个同学(编号为 1 到 n )正在 玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为 i 的同学的信息传递对象是编号为 Ti 的同学。

游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息, 但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自 己的生日时,游戏结束。请问该游戏一共可以进行几轮?

输入格式

共2行。

第1行包含1个正整数 n ,表示 n 个人。

第2行包含 n 个用空格隔开的正整数 T1,T2,⋯⋯,Tn ,其中第 i 个整数 Ti 表示编号为 i 的同学的信息传递对象是编号为 Ti 的同学, inTi=i

输出格式

1个整数,表示游戏一共可以进行多少轮。

输入输出样例

输入 #1
5
2 4 2 3 1
输出 #1
3

说明/提示

样例1解释

游戏的流程如图所示。当进行完第3 轮游戏后, 4号玩家会听到 2 号玩家告诉他自己的生日,所以答案为 3。当然,第 3 轮游戏后,2号玩家、 3 号玩家都能从自己的消息来源得知自己的生日,同样符合游戏结束的条件。

对于 30%的数据, n≤200;

对于 60%的数据, n≤2500;

对于100%的数据, n≤200000。

这道题其实是找最小的环,对应方案是并查集加图的遍历,我们把每个新的结点存在并查集中,一旦发放父节点相同说明出现了环,再记录路程,输出最小路程即可,是指在生成并查集的时候,要同时更新距离

AC code

#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
//#define re register
//#define ll long long
//#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read();
并查集的查找
//int found(int k);
辗转相除法------返回最大公因数
//int gcd(int p,int q);
阶乘
//int fac(int k);
st表
//int st[10000][30];
//int lg[10000];
初始化
//void initST(int n);
查找
//int seekST(int le, int ri);
线性基
//ll p[101];
添加
//void add_key(ll x);
//快速幂
//ll ksm(ll a, ll b){//    ll c = 1;
//    while(b){//        if(b % 2 == 1){//            c *= a;
//        }
//        a *= a;
//        b >>= 1;
//    }
//    return c;
//}
//线性筛
//void xxs(){//    bool nums[n];
//    for(int i = 2; i <= n; i++){//        if(!nums[i]){//            for(int j = i +i; j <= n; j+=i){//                nums[j] = 1;
//            }
//        }
//    }
//}
int n;
int f[200005], dis[200005];
int ans = 0x7fffffff;
int fa(int x){if(f[x] != x){int last = f[x];f[x] = fa(f[x]);dis[x] += dis[last];}return f[x];
}
void check(int a, int b){int x = fa(a), y = fa(b);if(x != y){f[x] = y;dis[a] = dis[b] + 1;}else{ans = min(ans,dis[a] + dis[b] + 1);
//        cout<<"test:"<<ans<<endl;}return;
}
int main()
{ios::sync_with_stdio(false);cin >> n;for(int i = 1; i <= n; i++){f[i] = i;}for(int i = 1; i <= n; i++){int x = i;int y;cin >>y;check(x,y);}cout<<ans<<endl;return 0;
}
//速读
inline int read()
{int x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;
}
并查集
//int f[1];
//int found(int k){//    if(f[k] == k){//        return k;
//    }
//    return f[k] = found(f[k]);
//}
辗转相除法
//int gcd(int p,int q){//  int t = p % q;
//  return t==0?q:gcd(q,t);
//}
阶乘
//int fac(int k){//    int ans = 1;
//    for(int i = 1; i<= k; i++){//        ans *= i;
//    }
//    return ans;
//}
初始化st表
//void initST(int n){//    for(int i = 1; i <= n; i++){//        int temp = r;
//        st[i + n][0] = st[i + n + n][0]= st[i][0] = temp;
//    }
//    for(int i = 2; i <= n * 3; i++){//        lg[i] = lg[i >> 1] + 1;
//    }
//    int ln = lg[n + n + n];
//    for(int i = 1; i <= ln; i++){//        for(int j = 1; j + (1 << (i - 1)) - 1<= n * 3; j++){//            st[j][i] = max(st[j][i-1],st[j+(1 << (i - 1))][i-1]);
//        }
//    }
//}
查找st表
//int seekST(int le, int ri){//    int len = ri - le + 1;
//    int q = lg[len];
//    return max(st[le][q],st[ri - (1 << q) + 1][q]);
//}
添加到线性基
//void add_key(ll x){//    for(int i = 62; i >= 0; i--)
//  {//      if(!(x >> (ll)i))
//          continue;
//      if(!p[i])
//      {//          p[i] = x;
//          break;
//      }
//      x ^= p[i];
//  }
//}

2、[USACO08DEC]Trick or Treat on the Farm G

题目描述

Every year in Wisconsin the cows celebrate the USA autumn holiday of Halloween by dressing up in costumes and collecting candy that Farmer John leaves in the N (1 <= N <= 100,000) stalls conveniently numbered 1…N.

Because the barn is not so large, FJ makes sure the cows extend their fun by specifying a traversal route the cows must follow. To implement this scheme for traveling back and forth through the barn, FJ has posted a ‘next stall number’ next_i (1 <= next_i <= N) on stall i that tells the cows which stall to visit next; the cows thus might travel the length of the barn many times in order to collect their candy.

FJ mandates that cow i should start collecting candy at stall i. A cow stops her candy collection if she arrives back at any stall she has already visited.

Calculate the number of unique stalls each cow visits before being forced to stop her candy collection.

POINTS: 100

输入格式

* Line 1: A single integer: N

* Lines 2…N+1: Line i+1 contains a single integer: next_i

输出格式

* Lines 1…N: Line i contains a single integer that is the total number of unique stalls visited by cow i before she returns to a stall she has previously visited.

题意翻译

题目描述

每年,在威斯康星州,奶牛们都会穿上衣服,收集农夫约翰在N(1<=N<=100,000)个牛棚隔间中留下的糖果,以此来庆祝美国秋天的万圣节。

由于牛棚不太大,FJ通过指定奶牛必须遵循的穿越路线来确保奶牛的乐趣。为了实现这个让奶牛在牛棚里来回穿梭的方案,FJ在第i号隔间上张贴了一个“下一个隔间”Next_i(1<=Next_i<=N),告诉奶牛要去的下一个隔间;这样,为了收集它们的糖果,奶牛就会在牛棚里来回穿梭了。

FJ命令奶牛i应该从i号隔间开始收集糖果。如果一只奶牛回到某一个她已经去过的隔间,她就会停止收集糖果。

在被迫停止收集糖果之前,计算一下每头奶牛要前往的隔间数(包含起点)。

输入格式

第1行 整数n。

第2行到n+1行 每行包含一个整数 next_i 。

输出格式

n行,第i行包含一个整数,表示第i只奶牛要前往的隔间数。

样例解释

有4个隔间

隔间1要求牛到隔间1

隔间2要求牛到隔间3

隔间3要求牛到隔间2

隔间4要求牛到隔间3

牛1,从1号隔间出发,总共访问1个隔间;

牛2,从2号隔间出发,然后到三号隔间,然后到2号隔间,终止,总共访问2个隔间;

牛3,从3号隔间出发,然后到2号隔间,然后到3号隔间,终止,总共访问2个隔间;

牛4,从4号隔间出发,然后到3号隔间,然后到2号隔间,然后到3号隔间,终止,总共访问3个隔间。

翻译提供者:吃葡萄吐糖

输入输出样例
输入 #1
4
1
3
2
3
输出 #1
1
2
2
3
说明/提示

Four stalls.

* Stall 1 directs the cow back to stall 1.

* Stall 2 directs the cow to stall 3

* Stall 3 directs the cow to stall 2

* Stall 4 directs the cow to stall 3

Cow 1: Start at 1, next is 1. Total stalls visited: 1.

Cow 2: Start at 2, next is 3, next is 2. Total stalls visited: 2.

Cow 3: Start at 3, next is 2, next is 3. Total stalls visited: 2.

Cow 4: Start at 4, next is 3, next is 2, next is 3. Total stalls visited: 3.

这道题感觉题解的大佬写得太好了,我就是临摹了一下然后加了个注释,具体看代码好了

AC code

#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
//#define re register
//#define ll long long
//#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read();
并查集的查找
//int found(int k);
辗转相除法------返回最大公因数
//int gcd(int p,int q);
阶乘
//int fac(int k);
st表
//int st[10000][30];
//int lg[10000];
初始化
//void initST(int n);
查找
//int seekST(int le, int ri);
线性基
//ll p[101];
添加
//void add_key(ll x);
//快速幂
//ll ksm(ll a, ll b){//    ll c = 1;
//    while(b){//        if(b % 2 == 1){//            c *= a;
//        }
//        a *= a;
//        b >>= 1;
//    }
//    return c;
//}
//线性筛
//void xxs(){//    bool nums[n];
//    for(int i = 2; i <= n; i++){//        if(!nums[i]){//            for(int j = i +i; j <= n; j+=i){//                nums[j] = 1;
//            }
//        }
//    }
//}
int color[100005];
int nums[100005];
int minc[100005];
int dfn[100005];
int sucdfn[100005];
int main()
{ios::sync_with_stdio(false);int n = r;for(int i = 1;i <= n; i++){nums[i] = r;}for(int i = 1; i <= n; i++){for(int j = i, cnt = 0;;j = nums[j], cnt++){//如果这个点还没走过,记录时间并记录所属的集合if(!color[j]){//记录走到这个点的时间dfn[j] = cnt;//上色color[j] = i;}//如果走到和自己相同的已经走过的点,说明走了一个完整的环,直接输出目前的步数即可else if(color[j] == i){//环的大小就是当前时间减去入环时间minc[i] = cnt - dfn[j];//记录从i点入环的时间sucdfn[i] = dfn[j];cout<<cnt<<endl;break;}//如果遇到环,但自己走的路不是完整的路else{//显然环的大小就是j点成环的环的大小minc[i] = minc[color[j]];//这里判断这个点是环上的点还是环外的点//如果是环上的点,入环的时间戳为0,因为本身就在环上,如果是环外的点,会走几步再入环,这里计算的就是走的步数sucdfn[i] = cnt + max(sucdfn[color[j]] - dfn[j],0);cout<<sucdfn[i] + minc[i]<<endl;break;}}}return 0;
}
//速读
inline int read()
{int x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;
}
并查集
//int f[1];
//int found(int k){//    if(f[k] == k){//        return k;
//    }
//    return f[k] = found(f[k]);
//}
辗转相除法
//int gcd(int p,int q){//  int t = p % q;
//  return t==0?q:gcd(q,t);
//}
阶乘
//int fac(int k){//    int ans = 1;
//    for(int i = 1; i<= k; i++){//        ans *= i;
//    }
//    return ans;
//}
初始化st表
//void initST(int n){//    for(int i = 1; i <= n; i++){//        int temp = r;
//        st[i + n][0] = st[i + n + n][0]= st[i][0] = temp;
//    }
//    for(int i = 2; i <= n * 3; i++){//        lg[i] = lg[i >> 1] + 1;
//    }
//    int ln = lg[n + n + n];
//    for(int i = 1; i <= ln; i++){//        for(int j = 1; j + (1 << (i - 1)) - 1<= n * 3; j++){//            st[j][i] = max(st[j][i-1],st[j+(1 << (i - 1))][i-1]);
//        }
//    }
//}
查找st表
//int seekST(int le, int ri){//    int len = ri - le + 1;
//    int q = lg[len];
//    return max(st[le][q],st[ri - (1 << q) + 1][q]);
//}
添加到线性基
//void add_key(ll x){//    for(int i = 62; i >= 0; i--)
//  {//      if(!(x >> (ll)i))
//          continue;
//      if(!p[i])
//      {//          p[i] = x;
//          break;
//      }
//      x ^= p[i];
//  }
//}

二、迪杰斯特拉算法

单源最短路径算法,感觉思路和Floyd算法有些相似之处。

1、【模板】单源最短路径(标准版)

题目背景

2018 年 7 月 19 日,某位同学在 NOI Day 1 T1 归程 一题里非常熟练地使用了一个广为人知的算法求最短路。

然后呢?

100→60;

Ag→Cu;

最终,他因此没能与理想的大学达成契约。

小 F 衷心祝愿大家不再重蹈覆辙。

题目描述

给定一个 n 个点,m 条有向边的带非负权图,请你计算从 s 出发,到每个点的距离。

数据保证你能从 s 出发到任意点。

输入格式

第一行为三个正整数 n,m,s。 第二行起 m 行,每行三个非负整数 ui,vi,wi,表示从 uivi 有一条权值为 wi 的有向边。

输出格式

输出一行 n 个空格分隔的非负整数,表示 s 到每个点的距离。

输入输出样例

输入 #1
4 6 1
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4
输出 #1
0 2 4 3

说明/提示

样例解释请参考 数据随机的模板题。

1≤n≤10^5;

1≤m≤2×10^5;

s=1;

1≤ui,vin

0≤wi≤109,

0≤∑wi≤109。

本题数据可能会持续更新,但不会重测,望周知。

2018.09.04 数据更新 from @zzq

这道题具体的迪杰斯特拉算法已经在注释里比较清楚地解释了,这里讲一下新用到的数据结构:priority_queue 优先队列,默认是大根堆,这里用这种数据结构来排序自然比我们一般情况下扫描一遍要快非常多,所以我们要制作pair,前面的参数就是用来排序的。

AC code

#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read();
int fr[100010], to[200010], nex[200010], v[200010], tl, d[100010];
bool vis[100010];
void add(int x, int y, int w){//前往的点to[++tl] = y;//这条路径的权值v[tl] = w;//记录通往x的所有前驱nex[tl] = fr[x];//类似指针,不断向后推移,最后通过前驱就能回溯所有的能到x的点fr[x] = tl;
}
priority_queue <pair<int, int> > q;
int main()
{ios::sync_with_stdio(false);int n,m,x,y,z,s;cin >> n>> m>> s;for(int i = 1 ; i <= m; i++){cin >> x >> y >>z;//添加路径add(x, y, z);}for(int i = 1; i <= n; i++){//把所有的点初始化为死路,即最短路径非常大d[i] = 1e10;}//第一个点的距离自然是0d[s] = 0;q.push(make_pair(0,s));while(!q.empty()){//从队列里拿出一个点搜索,注意,拿出来的点其实是已经确定最短路径的点,这里我们要做的是搜索和这个点相连的点,优化这些点的路径int x = q.top().second;q.pop();//如果这个点搜过了,就跳过(因为搜过的点是已经确定为最佳路径的点)if(vis[x]){continue;}vis[x] = true;for(int i = fr[x]; i; i = nex[i]){int y = to[i], l=v[i];if(d[y] > d[x] + l){//如果符合条件就添加进去然后继续搜索d[y] = d[x] + l;q.push(make_pair(-d[y],y));}}}for(int i = 1; i <= n; i++){cout<<d[i]<<" ";}return 0;
}
//速读
inline int read()
{int x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;
}

2、最短路计数

题目描述

给出一个N个顶点M条边的无向无权图,顶点编号为1−N。问从顶点11开始,到其他每个点的最短路有几条。

输入格式

第一行包含2个正整数N,M,为图的顶点数与边数。

接下来M行,每行2个正整数x,y,表示有一条顶点x连向顶点y的边,请注意可能有自环与重边。

输出格式

N行,每行一个非负整数,第ii行输出从顶点1到顶点i有多少条不同的最短路,由于答案有可能会很大,你只需要输出ans%100003后的结果即可。如果无法到达顶点i则输出0。

输入输出样例

输入 #1
5 71 21 32 43 42 34 54 5
输出 #1
11124

说明/提示

1到5的最短路有4条,分别为2条1-2-4-5和1−3−4−5(由于4−5的边有2条)。

对于20%的数据,N≤100;

对于60%的数据,N≤1000;

对于100%的数据,N<=1000000,M<=2000000。

算单源最短路的话就直接迪杰斯特拉算法,这里让计数,就是我们在算迪杰斯特拉的时候,会扫过一些已经确定路线的点,如果发生了路线的更新,那就得把答案从头加,然后如果已经是最短路径的话,就把现在已经统计的路数相加即可

AC code

#include <bits/stdc++.h>
#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
using namespace std;
const int N = 1000005;
//速读
inline int read()
{int x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;
}
//struct edge{//    int v, next;
//}e[4000005];
int cnt = 0;
int head[N], nxt[N], to[N];
void add(int x,int y)
{to[++cnt]=y;nxt[cnt]=head[x];head[x]=cnt;
}
bool vis[N];
int ans[N], dis[N];
priority_queue< pair< int,int > > q;
int mod=100003;
int main()
{ios::sync_with_stdio(false);int n = r;int m = r;for(int i=1;i<=m;i++){int x=r;int y=r;add(x,y);add(y,x);}for(int i=1;i<=n;i++){dis[i]=1e9;vis[i]=0;}dis[1]=0;ans[1]=1;q.push(make_pair(0,1));while(q.size()){int x=q.top().second;q.pop();if(vis[x])    continue;vis[x]=1;for(int i=head[x];i;i=nxt[i]){int v = to[i];if(dis[v]>dis[x]+1){dis[v]=dis[x]+1;ans[v]=ans[x];q.push(make_pair(-dis[v],v));}else if(dis[v]==dis[x]+1){ans[v]+=ans[x];ans[v]%=mod;}}}for(int i = 1; i <= n; i++){cout<<ans[i]<<endl;}return 0;
}

3、通往奥格瑞玛的道路

题目背景

在艾泽拉斯大陆上有一位名叫歪嘴哦的神奇术士,他是部落的中坚力量

有一天他醒来后发现自己居然到了联盟的主城暴风城

在被众多联盟的士兵攻击后,他决定逃回自己的家乡奥格瑞玛

题目描述

在艾泽拉斯,有n个城市。编号为1,2,3,…,n。

城市之间有m条双向的公路,连接着两个城市,从某个城市到另一个城市,会遭到联盟的攻击,进而损失一定的血量。

每次经过一个城市,都会被收取一定的过路费(包括起点和终点)。路上并没有收费站。

假设1为暴风城,n为奥格瑞玛,而他的血量最多为b,出发时他的血量是满的。

歪嘴哦不希望花很多钱,他想知道,在可以到达奥格瑞玛的情况下,他所经过的所有城市中最多的一次收取的费用的最小值是多少。

输入格式

第一行3个正整数,n,m,b。分别表示有n个城市,m条公路,歪嘴哦的血量为b。

接下来有n行,每行1个正整数,fi。表示经过城市i,需要交费fi元。

再接下来有m行,每行3个正整数,ai,bi,ci(1<=ai,bi<=n)。表示城市ai和城市bi之间有一条公路,如果从城市ai到城市bi,或者从城市bi到城市ai,会损失ci的血量。

输出格式

仅一个整数,表示歪嘴哦交费最多的一次的最小值。

如果他无法到达奥格瑞玛,输出AFK。

输入输出样例

输入 #1
4 4 8856102 1 22 4 11 3 43 4 3
输出 #1
10

说明/提示

对于60%的数据,满足n≤200,m≤10000,b≤200

对于100%的数据,满足n≤10000,m≤50000,b≤1000000000

对于100%的数据,满足ci≤1000000000,fi≤1000000000,可能有两条边连接着相同的城市。

这题我快写吐了┭┮﹏┭┮,但对自己来说提高蛮大的,其实二分加单源最短路的思路还是挺明显,感觉是自己之前写迷的时候还可以看看题解,这回是纯手写,感觉对自己理清思路非常有帮助,有了前面那些题的铺垫,这道题我觉得大家也可以试试自己搞定。

AC code

#include <bits/stdc++.h>
#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read()
{int x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;
}
const int N = 100005;
ll M = 1000000005;
ll head[N], nxt[N], to[N], v[N];
ll tot = 0;
ll f[10005], g[10005];
int n, m, b;
void add(ll x, ll y, ll w){to[++tot] = y;v[tot] = w;nxt[tot] = head[x];head[x] = tot;
}
ll d[10005];
bool vis[10005];
priority_queue <pair<ll, int> > q;
bool check(ll k){//    cout<<"k:"<<k<<endl;if(k < f[1] || k < f[n]) return false;for(int i = 1; i <= n; i++){d[i] = M;vis[i] = 0;
//        cout<<"d["<<i<<"]:"<<d[i]<<endl;}d[1] = 0;q.push(make_pair(0,1));while(!q.empty()){int x = q.top().second;q.pop();if(vis[x]){continue;}
//        cout<<d[x]<<"***"<<x<<endl;vis[x] = true;for(int i = head[x]; i; i = nxt[i]){int y = to[i], l = v[i];if(d[y] > d[x] + l && f[y] <= k){d[y] = d[x] + l;
//                cout<<" x: "<<x<<" y: "<<y<<" d[y]: "<<d[y]<<" [x]: "<<d[x]<<" l: "<<l<<endl;q.push(make_pair(-d[y], y));}}}if(d[n] > b){//            cout<<"false: d[n] = "<<d[n]<<endl;return false;}else{//        cout<<"d[n]:"<<d[n]<<endl;return true;}
}
int main()
{ios::sync_with_stdio(false);n = r;m = r;b = r;for(int i = 1; i <= n; i++){f[i] = r;g[i] = f[i];}sort(g + 1, g + 1 + n);for(int i = 1; i <= m; i++){ll x = r;ll y = r;ll w = r;if(x == y){continue;}add(x, y, w);add(y, x, w);}if(!check(g[n])){cout<<"AFK"<<endl;return 0;}int ri = n;int le = 1;ll ans = g[n];
//    cout<<ans<<endl;while(ri >= le){int mid = (ri + le) >> 1;
//            cout<<mid<<endl;if(check(g[mid])){ans = g[mid];
//            cout<<ans<<endl;ri = mid - 1;
//            cout<<"r:"<<mid<<endl;}else{//                cout<<"w:"<<mid<<endl;le = mid + 1;}}cout<<ans<<endl;return 0;
}

4、速度限制

题目描述

在这个繁忙的社会中,我们往往不再去选择最短的道路,而是选择最快的路线。开车时每条道路的限速成为最关键的问题。不幸的是,有一些限速的标志丢失了,因此你无法得知应该开多快。一种可以辩解的解决方案是,按照原来的速度行驶。你的任务是计算两地间的最快路线。

你将获得一份现代化城市的道路交通信息。为了使问题简化,地图只包括路口和道路。每条道路是有向的,只连接了两条道路,并且最多只有一块限速标志,位于路的起点。两地 AB,最多只有一条道路从 A 连接到 B。你可以假设加速能够在瞬间完成并且不会有交通堵塞等情况影响你。当然,你的车速不能超过当前的速度限制。

输入格式

第一行是 3 个整数 NMD (2≤N≤150),表示道路的数目,用0 N−1 标记。M 是道路的总数,D 表示你的目的地。

接下来的 M 行,每行描述一条道路,每行有 4 个整数 A (0≤A<N),B (0≤B<N),V (0≤V≤500) 和 L (1≤L≤500),这条路是从 AB 的,速度限制是 V,长度为 L。如果 V 是 0,表示这条路的限速未知。

如果 V 不为 0,则经过该路的时间 T=VL。否则 T=Vold 是你到达该路口前的速度。开始时你位于 0 点,并且速度为 70。

输出格式

输出文件仅一行整数,表示从 0 到 D 经过的城市。

输出的顺序必须按照你经过这些城市的顺序,以 0 开始,以 D 结束。仅有一条最快路线。

输入输出样例

输入 #1
6 15 10 1 25 680 2 30 500 5 0 1011 2 70 771 3 35 422 0 0 222 1 40 862 3 0 232 4 45 403 1 64 143 5 0 234 1 95 85 1 0 845 2 90 645 3 36 40
输出 #1
0 5 2 3 1

迪杰斯特拉算法加分层图,大概就是对不同速度的结果单独处理最后比较哪个更好。然后要注意因为最后要回溯,所以要存储每个点的各种速度的前一个结点

AC code

#include <bits/stdc++.h>
#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read()
{int x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;
}
int tot = 0;
const int N = 100001;
int to[N], len[N], nxt[N], head[N], tim[N];
void add(int x, int y, int t, int l){to[++tot] = y;tim[tot] = t;len[tot] = l;nxt[tot] = head[x];head[x] = tot;
}
//存储上一个点的速度
struct Nodee{int x,v;
}from[1001][1001];
//分层图 : 出发点为i,速度为j
int vis[1001][1001];
double dis[1001][1001];
//时间 点 速度
priority_queue<pair<double,pair<int,int> > >q;
void out(int x,int v){if(x==1) return;out(from[x][v].x,from[x][v].v);cout<<x-1<<" ";
}
int main()
{ios::sync_with_stdio(false);int n = r, m = r, d = r;d++;for(int i = 1; i <= m; i++){int x = r, y = r, t = r, l = r;x++;y++;add(x, y, t, l);}q.push(make_pair(0,make_pair(1,70)));for(int i = 1; i <= n + 1; i++){for(int j = 1; j <= 1000; j++){dis[i][j] = 1e9+1;}}dis[1][70] = 0;vis[1][70] = 1;while(!q.empty()){int x = q.top().second.first;int vs = q.top().second.second;vis[x][vs] = 0;q.pop();for(int i = head[x]; i ; i = nxt[i]){int y = to[i];int ys = tim[i];if(ys){if(dis[y][ys] > dis[x][vs] + (double)len[i] / (double)ys){dis[y][ys] = dis[x][vs] + (double)len[i] / (double)ys;from[y][ys]={x,vs};if(vis[y][ys]){continue;}vis[y][ys] = 1;q.push(make_pair(-dis[y][ys], make_pair(y,ys)));}}else{ys = vs;if(dis[y][ys] > dis[x][vs] + (double)len[i] / (double)ys){dis[y][ys] = dis[x][vs] + (double)len[i] / (double)ys;from[y][ys]={x,vs};if(vis[y][ys]){continue;}vis[y][ys] = 1;q.push(make_pair(-dis[y][ys], make_pair(y,ys)));}}}}int mi = 0;dis[d][mi] = 1e9+100;for(int i = 1; i <= 1000; i++){if(dis[d][mi] >= dis[d][i] && dis[d][i] != 1e9+1){mi = i;}}cout<<"0 ";out(d,mi);cout<<endl;return 0;
}

三、Johnson算法

1、【模板】Johnson 全源最短路

题目描述

给定一个包含 n 个结点和 m 条带权边的有向图,求所有点对间的最短路径长度,一条路径的长度定义为这条路径上所有边的权值和。

注意:

  1. 边权可能为负,且图中可能存在重边和自环;
  2. 部分数据卡 n 轮 SPFA 算法。

输入格式

第 1 行:2 个整数 n,m,表示给定有向图的结点数量和有向边数量。

接下来 m 行:每行 3 个整数 u,v,w,表示有一条权值为 w 的有向边从编号为 u 的结点连向编号为 v 的结点。

输出格式

若图中存在负环,输出仅一行 −1。

若图中不存在负环:

输出 n 行:令 dis{i,j}为从 ij 的最短路,在第 i 行输出 第i到其他点的权值和,注意这个结果可能超过 int 存储范围。

如果不存在从 ij 的路径,则 dis_{i,j}=10^9;如果 i=j,i=j,则 dis{i,j}=0。

输入输出样例

输入 #1

5 7
1 2 4
1 4 10
2 3 7
4 5 3
4 2 -2
3 4 -3
5 3 4

输出 #1

128
1000000072
999999978
1000000026
1000000014

输入 #2

5 5
1 2 4
3 4 9
3 4 -3
4 5 3
5 3 -2

输出 #2

-1

说明/提示

【样例解释】

左图为样例 1 给出的有向图,最短路构成的答案矩阵为:

0 4 11 8 11 1000000000 0 7 4 7 1000000000 -5 0 -3 0 1000000000 -2 5 0 3 1000000000 -1 4 1 0

右图为样例 2 给出的有向图,红色标注的边构成了负环,注意给出的图不一定连通。

【数据范围】

对于 100% 的数据,1≤n≤3×10^3, 1≤m≤6×10^3, 1≤u,vn, −3×105≤*w*≤3×105。

对于 20% 的数据,1≤n≤100,不存在负环(可用于验证 Floyd 正确性)

对于另外 20% 的数据,w≥0(可用于验证 Dijkstra 正确性)

upd. 添加一组 Hack 数据:针对 SPFA 的 SLF 优化

这道题用spfa算法从思路上来讲是能解决问题的,但题干也说了会卡,再看迪杰斯特拉算法,显然会被他的负数卡,然后就有一种很神奇的思路,我们先初始化一个0点,对他做spfa,相当于定了个0势能点,然后每个边的权值加上起点权值减终点权值,然后用迪杰斯特拉就能算了,最后求和的时候再把势能减掉就行了

AC code

#include <bits/stdc++.h>
#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
#define MAX 1e9
using namespace std;
//速读
inline int read()
{int x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;
}
//构建边的结构体
struct edge{int v, w, next;
}e[10005];
//构造节点
struct node{int dis, id;//距离和编号//运算符重载,方便排序bool operator < (const node& a) const {return dis > a.dis;}//构造函数node(int d, int i){dis = d, id = i;}
};
//vis 记录是否访问了
//head 记录每个点的前缀
//t 记录每个点被松弛的次数
int head[5005], vis[5005], t[5005];
//cnt 边数
//n 结点数量
//m 有向边数量
int cnt, n, m;
// h 势能(即0到该点的距离,通过这种方式让所有点恒正,最后再减去)
ll h[5005], dis[5005];
//添加边
void addE(int u, int v, int w){//前往的节点e[++cnt].v = v;//权值e[cnt].w =  w;//u节点的上一个出发地点e[cnt].next = head[u];//u节点的出发点head[u] = cnt;
}
//spfa算法,有点Floyd内味,传入一个预设的0点
bool spfa(int s){//初始化队列queue<int> q;memset(h, 63, sizeof(h));//更新状态h[s] = 0;vis[s] = 1;q.push(s);//开始宽搜,直到每个点都是最佳状态while(!q.empty()){int u = q.front();q.pop();//因为这个点被拿出来了,队列里面就没这个点了vis[u] = 0;for(int i = head[u]; i; i = e[i].next){int v = e[i].v;//如果可以变短,就松弛if(h[v] > h[u] + e[i].w){h[v] = h[u] + e[i].w;if(!vis[v]){q.push(v);vis[v] = 1;t[v]++;//如果松弛 n + 1 次,成环if(t[v] == n + 1){return false;}}}}}return true;
}
void dijkstra(int s){//初始化priority_queue<node> q;for(int i  = 1; i <= n; i++){dis[i] = MAX;}memset(vis, 0, sizeof(vis));dis[s] = 0;q.push(node(0,s));while(!q.empty()){int u = q.top().id;q.pop();if(vis[u]){continue;}vis[u] = 1;//搜索所有的出发点for(int i = head[u]; i; i = e[i].next){int v = e[i].v;if(dis[v] > dis[u] + e[i].w){dis[v] = dis[u] + e[i].w;if(!vis[v]){q.push(node(dis[v],v));}}}}return;
}
int main()
{ios::sync_with_stdio(false);cin >> n >> m;for(int i = 1; i <= m; i++){int u, v, w;cin >> u >> v >> w;addE(u,v,w);}for(int i = 1; i <= n; i++){addE(0,i,0);}if(!spfa(0)){cout<<"-1"<<endl;return 0;}//Johnson算法,之前的spfa就在为这个铺垫, 通过这个转化就能把值全变成正的,最后再减去即可for(int u = 1; u <= n; u++){for(int i = head[u]; i ; i = e[i].next){e[i].w += h[u] - h[e[i].v];}}for(int i = 1; i <= n; i++){dijkstra(i);ll ans = 0;for(int j = 1; j <= n; j++){if(dis[j] == MAX){ans += j * MAX;}else{ans += j * (dis[j] + h[j] - h[i]);}}cout<<ans<<endl;}return 0;
}

图论 ~%?…,# *‘☆℃$︿★?入门之章相关推荐

  1. [菜鸟SpringCloud实战入门]第九章:服务网关Zuul体验

    前言 欢迎来到菜鸟SpringCloud实战入门系列(SpringCloudForNoob),该系列通过层层递进的实战视角,来一步步学习和理解SpringCloud. 本系列适合有一定Java以及Sp ...

  2. webrtc 入门第二章 音视频录制

    webrtc 入门第二章 音视频录制 一.介绍 1.媒体录制原理 ​ 在很多场景中回放音视频资源的需求是非常重要的例如会议,直播授课等.任何媒体形式的表情都可进行录制,如 ,,等.其中内容更加自由用户 ...

  3. 计算机输入法入门,电脑入门——第章 学习中文输入法.doc

    电脑入门--第章 学习中文输入法 4.1 智能ABC输入法 智能ABC输入法(又称标准输入法)是中文Windows操作系统自带的一种汉字输入法.智能ABC输入法编码时采用汉语拼音编码方案,将汉字编码与 ...

  4. WPF编程基础入门 ——— 第二章 XAML

    XAML 简述 XAML(eXtensible Application Markup Language,可扩展应用程序标记语言)是微软公司创建的一种新的描述性语言,用于搭建应用程序用户界面.XAML实 ...

  5. jQuery入门第一章(jQuery初体验)

    JQ 基本概念 jQuery 其实就是别的团队封装好的一个 JS 文件. 常见错误 没有引入 jQuery 文件,引入失败,请检查 jquery.js 文件的路径. JQ 对象 和 DOM 对象互相转 ...

  6. Python入门——第一章 python编程基础

    Python入门 文章目录 Python入门 第一章 python编程基础 1.1 基本输入输出 1.1.1使用print()函数进行简单输出 chr()函数 print()输出到指定文件 print ...

  7. WebApi入门第一章(WebApi介绍)

    本文部分内容参考官方文档 1.WebAPI概念介绍 API(Application Programming Interface,应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于 ...

  8. 蓝桥杯比赛常考算法_备战蓝桥--算法竞赛入门第一章总结

    笔者备战蓝桥杯先打算看完<算法竞赛入门经典>第2版,在这里写下第一章的笔记,供自己和大家参考. 鸡兔同笼问题 原题: 已知鸡和兔的总数量为n,总腿数为m.输入n和m,依次输出鸡的数目和兔的 ...

  9. [算法竞赛入门]第一章_算法概述

    1 第1部分 语 言 篇2 3 第1章 程序设计入门4 [学习内容相关章节]5 1.1算术表达式 1.2变量及其输入 1.3顺序结构程序设计6 1.4分支结构程序设计 1.5C/C++编码规范 1.6 ...

最新文章

  1. python实现维吉尼亚加密法
  2. 【推荐】揭秘谷歌电影票房预测模型
  3. 编程方法学15:指针要点回顾
  4. mysql online ddl和pt_MySQL变更之:Online DDL 和 PT-OSC 该选谁?
  5. [css] 如何清除在项目中无用的css代码呢?
  6. OpenShift 4 - 用KubeletConfig和ContainerRuntimeConfig分别修改集群节点的Kubelet和cri-o的配置
  7. 大数据_MapperReduce_协处理器_类似Mysql的触发器---Hbase工作笔记0024
  8. 数据分析师熬夜整理:最全「零售业」数据指标和使用技巧
  9. Origami 用于Quartz 的免费的交互设计框架
  10. 【Java学习笔记之三】java中的变量和常量
  11. OpenAnolis社区致Linux开发者的一封信
  12. 吴恩达机器学习视频笔记和编程作业(Python实现)汇总
  13. Java程序性能优化- 让你的Java程序更快、更稳定pdf
  14. java计算机毕业设计风情旅游网站源码+mysql数据库+系统+lw文档+部署
  15. 论文相关------如何在论文写作中使用拉丁文简写
  16. 利用matlab进行图片的二值化处理
  17. Google Earth 使用的经纬度格式及转换
  18. CSS animation 属性
  19. 华为大数据客户端安装步骤
  20. 因果推断与因果性学习研究进展

热门文章

  1. python 可选参数
  2. 如何删除lok正常开启weblogic managed server
  3. XL1509-5.0E1芯片温度为75度。参数12V、0.6A
  4. 【JVM】JVM之执行引擎
  5. Echarts legend翻页滚动形式
  6. 全链路跟踪之线程上下文Thread Local实战(完整源码)
  7. 定了!家庭教育指导师全国统一培训考核,报名通道今日开启!
  8. 如何进行移动设备资产管理
  9. 固定资产管理系统能帮助企业解决哪些问题?
  10. MD5碰撞和我眼中的MD5