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

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

一、图的遍历与存储

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解释

img

游戏的流程如图所示。当进行完第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;
    }
    //第一个点的距离自然是0
    d[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 给出的有向图,红色标注的边构成了负环,注意给出的图不一定连通。

img

【数据范围】

对于 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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值