acwing提高最短路

单源最短路的建图方式

920. 最优乘车

在这里插入图片描述
思路
1.输入:由于不知道一条巴士路线的车站数量所以需要用到getline();
它有两种形式:一种是头文件< istream >中输入流成员函数;一种在头文件< string >中普通函数;
< istream >中的getline

cin.getline(char* s, streamsize n );
// 从istream中读取至多n个字符(包含结束标记符)保存在s对应的数组中。即使还没读够n个字符,注意是char[]
cin.getline(char*s, streamsize n, char delim);
//如果遇到delim 或 字数达到限制,则读取终止,注意delim都不会被保存进s对应的数组中。

< string > 中的getline

getline (istream&  is, string& str);
// is :表示一个输入流,例如 cin; str :string类型的引用,用来存储输入流中的流信息。
getline (istream&  is, string& str, char delim);
//delim :char类型的变量,所设置的截断字符;在不自定义设置的情况下,遇到'\n',则终止输入

< sstream > 中的stringstream

istringstream::istringstream(string str);
istringstream >> t //向流中传值
istringstream << result; //向result中传值
//stringstream 对象用于输入一行字符串,以  空格  为分隔符把该行分隔开来

2.换乘多少次 可以转换为 乘坐过多少次车减1

1、在同一条路线中,任意一个在此路线上的车站均能沿着该路线的方向到达后面的车站,权值都是1,表示只乘坐一次车

2、通过建图,由于权值均是1,使用bfs求出1号点到n号点最少乘过多少次车。

输入样例:

3 7
6 7
4 7 3 6
2 1 3 5

输出样例:

2

代码模板

#include <cstring>
#include <iostream>
#include <algorithm>
#include <sstream>

using namespace std;

const int N = 510;

int m, n;
bool g[N][N];
int dist[N];
int stop[N];
int q[N];

void bfs()
{
    int hh = 0, tt = 0;
    memset(dist, 0x3f, sizeof dist);
    q[0] = 1;
    dist[1] = 0;

    while (hh <= tt)
    {
        int t = q[hh ++ ];

        for (int i = 1; i <= n; i ++ )
            if (g[t][i] && dist[i] > dist[t] + 1)
            {
                dist[i] = dist[t] + 1;
                q[ ++ tt] = i;
            }
    }
}

int main()
{
    cin >> m >> n;

    string line;
    getline(cin, line); //读掉第一个回车
    while (m -- )
    {
        getline(cin, line); //读入一行
        stringstream ssin(line); 
        int cnt = 0, p;
        while (ssin >> p) stop[cnt ++ ] = p;
        for (int j = 0; j < cnt; j ++ )
            for (int k = j + 1; k < cnt; k ++ )
                g[stop[j]][stop[k]] = true;
    }

    bfs();

    if (dist[n] == 0x3f3f3f3f) puts("NO");
    else cout << max(dist[n] - 1, 0) << endl;

    return 0;
}

 

903. 昂贵的聘礼

在这里插入图片描述
思路
1.酋长的地位不是最高的,所以还要枚举合法区间
2.建立虚拟原点 购买该物品一直优惠到物品1的最短路

输入样例:

1 4
10000 3 2
2 8000
3 5000
1000 2 1
4 200
3000 2 1
4 200
50 2 0

输出样例:

5250

代码模板

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

int n, m;
int w[N][N], level[N];
int dist[N];
bool st[N];

int dijkstra(int down, int up)
{
    memset(dist, 0x3f, sizeof dist);
    memset(st, 0, sizeof st);

    dist[0] = 0; //虚拟原点
    for (int i = 1; i <= n + 1; i ++ )
    {
        int t = -1;
        for (int j = 0; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                 t = j;

        st[t] = true;
        for (int j = 1; j <= n; j ++ )
            if (level[j] >= down && level[j] <= up)
                dist[j] = min(dist[j], dist[t] + w[t][j]);
    }

    return dist[1];
}

int main()
{
    cin >> m >> n;

    memset(w, 0x3f, sizeof w);
    for (int i = 1; i <= n; i ++ ) w[i][i] = 0; //每个点到自己的距离

    for (int i = 1; i <= n; i ++ )
    {
        int price, cnt;
        cin >> price >> level[i] >> cnt;
        w[0][i] = min(price, w[0][i]);  //直接买该物品
        while (cnt -- )
        {
            int id, cost;
            cin >> id >> cost;
            w[id][i] = min(w[id][i], cost);
        }
    }

    int res = INF;
    //枚举区间
    for (int i = level[1] - m; i <= level[1]; i ++ ) res = min(res, dijkstra(i, i + m));

    cout << res << endl;

    return 0;
}

 

单源最短路的综合应用

1135. 新年好(dfs)

在这里插入图片描述
1.首先用spfa被卡了
2.

输入样例:

6 6
2 3 4 5 6
1 2 8
2 3 3
3 4 4
4 5 5
5 6 2
1 6 7

输出样例:

21

代码模板

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

typedef pair<int, int> PII;

const int N = 50010, M = 200010, INF = 0x3f3f3f3f;

int n, m;
int h[N], e[M], w[M], ne[M], idx;
int q[N], dist[6][N];
int source[6];
bool st[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

void dijkstra(int start, int dist[])
{
    memset(dist, 0x3f, N * 4);
    dist[start] = 0;
    memset(st, 0, sizeof st);

    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, start});

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.second;
        if (st[ver]) continue;
        st[ver] = true;

        for (int i = h[ver]; ~i; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[ver] + w[i])
            {
                dist[j] = dist[ver] + w[i];
                heap.push({dist[j], j});
            }
        }
    }
}

int dfs(int u, int start, int distance)
{
    if (u > 5) return distance;

    int res = INF;
    for (int i = 1; i <= 5; i ++ )
        if (!st[i])
        {
            int next = source[i];
            st[i] = true;
            res = min(res, dfs(u + 1, i, distance + dist[start][next]));
            st[i] = false;
        }

    return res;
}

int main()
{
    scanf("%d%d", &n, &m);
    source[0] = 1;
    for (int i = 1; i <= 5; i ++ ) scanf("%d", &source[i]);

    memset(h, -1, sizeof h);
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c), add(b, a, c);
    }

    for (int i = 0; i < 6; i ++ ) dijkstra(source[i], dist[i]);

    memset(st, 0, sizeof st);
    printf("%d\n", dfs(1, 0, 0));

    return 0;
}

 

340. 通信线路(二分)

在这里插入图片描述
思路
题目转换为寻找 1-N 路径中第 K + 1 大最小的值,继续转换为:设定一个bound【0-1000001】,求1-N最少经过的长度大于x的边的数量是否小于等于k,如果小于k则bound还可以缩小,否则不可缩小,用二分寻找最小值。
所有边分类所有大于bound的边权为1,所有小于bound的边权为0,边权只有0-1,用双端队列。
时间复杂度O(n+m)logL = 200万左右
输入样例:

5 7 1
1 2 5
3 1 4
2 4 8
3 2 3
5 2 9
3 4 7
4 5 6

输出样例:

4

代码模板

#include <cstring>
#include <iostream>
#include <algorithm>
#include <deque>

using namespace std;

const int N = 1010, M = 20010;

int n, m, k;
int h[N], e[M], w[M], ne[M], idx;
int dist[N];
deque<int> q;
bool st[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

bool check(int bound)
{
    memset(dist, 0x3f, sizeof dist);
    memset(st, 0, sizeof st);

    q.push_back(1);
    dist[1] = 0;

    while (q.size())
    {
        int t = q.front();
        q.pop_front();

        if (st[t]) continue;
        st[t] = true;

        for (int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i], x = w[i] > bound;
            if (dist[j] > dist[t] + x)
            {
                dist[j] = dist[t] + x;
                if (!x) q.push_front(j);
                else q.push_back(j);
            }
        }
    }

    return dist[n] <= k;
}

int main()
{
    cin >> n >> m >> k;
    memset(h, -1, sizeof h);
    while (m -- )
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }

    int l = 0, r = 1e6 + 1;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }

    if (r == 1e6 + 1) cout << -1 << endl;
    else cout << r << endl;

    return 0;
}

 

342. 道路与航线(拓扑排序)

在这里插入图片描述
思路
1.关于spfa它又死了,不然是一个spfa模板题
2.所以想到只能用dijkstra 但是不能处理负权边
3.首先道路: 无负权边,双向 ->可以用跑dijkstra
其次航道: 有负权边,单向,且不可能有其他道路回到原来的地方(无环)->可以用拓扑排序优化
4.所以代码实现:输入所有道路,把道路连通块(城市内)当作一个团,并标记记录相应得团中得点,和点是属于哪个团得,团和团之间航道相连;
5.输入所有航道,并记录入度,以便拓扑排序。
6.按照拓扑排序依次处理每个连通块,先将入度为0得连通块编号加入队列
7.队头取出一个连通块编号,将该连通块得所有点跑dijkstra
8.跑dijkstra得过程中如果出队得点是该连通块内部的点则更新,非连通块内部的点则该连通块入度减1,如果该连通块入度==0则将该连通块编号加入拓扑排序队列中......
9.刚开始觉得每次把所有连通块的所有点入队跑dijkstra感觉有点问题,但是dijkstra小根堆优化,基于贪心策略,出队的必然是确认最小的方案,而原本入队的INF的点由于小根堆优化,出队时必然早已被更新,判重直接continue,所以可以总结出小根堆优化dijkstra不知道起点再何处可以直接将整个连通块所有点入队 跑dijkstra不会有任何问题。
所以本题的key:根据拓扑排序的顺序,本题的负权边用拓扑排序优化,正权连通块用dijkstra即可
10.时间复杂度主要是dijkstra 即O(mlogn)

输入样例:

6 3 3 4
1 2 5
3 4 5
5 6 10
3 5 -100
4 6 -100
1 3 -10

输出样例:

NO PATH
NO PATH
5
0
-95
-100

代码模板

#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 25010, M = 150010, INF = 0x3f3f3f3f;

int n, mr, mp, S;
int id[N];
int h[N], e[M], w[M], ne[M], idx;
int dist[N], din[N];
vector<int> block[N];
int bcnt;
bool st[N];
queue<int> q;

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

void dfs(int u, int bid)
{
    id[u] = bid, block[bid].push_back(u);

    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (!id[j])
            dfs(j, bid);
    }
}

void dijkstra(int bid)
{
    priority_queue<PII, vector<PII>, greater<PII>> heap;

    for (auto u : block[bid])
        heap.push({dist[u], u});

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.y, distance = t.x;
        if (st[ver]) continue;
        st[ver] = true;

        for (int i = h[ver]; ~i; i = ne[i])
        {
            int j = e[i];
            if (id[j] != id[ver] && -- din[id[j]] == 0) q.push(id[j]);
            if (dist[j] > dist[ver] + w[i])
            {
                dist[j] = dist[ver] + w[i];
                if (id[j] == id[ver]) heap.push({dist[j], j});
            }
        }
    }
}

void topsort()
{
    memset(dist, 0x3f, sizeof dist);
    dist[S] = 0;

    for (int i = 1; i <= bcnt; i ++ )
        if (!din[i])
            q.push(i);

    while (q.size())
    {
        int t = q.front();
        q.pop();
        dijkstra(t);
    }
}

int main()
{
    cin >> n >> mr >> mp >> S;
    memset(h, -1, sizeof h);

    while (mr -- )
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }

    for (int i = 1; i <= n; i ++ )
        if (!id[i])
        {
            bcnt ++ ;
            dfs(i, bcnt);
        }

    while (mp -- )
    {
        int a, b, c;
        cin >> a >> b >> c;
        din[id[b]] ++ ;
        add(a, b, c);
    }

    topsort();

    for (int i = 1; i <= n; i ++ )
        if (dist[i] > INF / 2) cout << "NO PATH" << endl;
        else cout << dist[i] << endl;

    return 0;
}

 

341. 最优贸易

在这里插入图片描述
思路
(SPFA) O(n+km)
先求出:

从 11 走到 ii 的过程中,买入水晶球的最低价格 dmin[i];
从 ii 走到 nn 的过程中,卖出水晶球的最高价格 dmax[i];
然后枚举每个城市作为买卖的中间城市,求出 dmax[i] - dmin[i] 的最大值即可。

求 dmin[i] 和 dmax[i] 时,由于不是拓扑图,状态的更新可能存在环,因此不能使用动态规划,只能使用求最短路的方式。
另外,我们考虑能否使用 dijkstra 算法,如果当前 dmin[i] 最小的点是 5,那么有可能存在边 5-> 6, 6-> 7, 7-> 5,假设当前 dmin[5] = 10,则有可能存在 6 的价格是11, 但 7 的价格是3,那么 dmin[5] 的值就应该被更新成3,因此当前最小值也不一定是最终最小值,所以dijkstra算法并不适用,我们只能采用 spfa 算法。

时间复杂度
瓶颈是SPFA,SPFA 算法的时间复杂度是 O(km),其中 kk 一般情况下是个很小的常数,最坏情况下是 n, n 表示总点数,m 表示总边数。因此总时间复杂度是 O(km)。

输入样例:

6 3 3 4
1 2 5
3 4 5
5 6 10
3 5 -100
4 6 -100
1 3 -10

输出样例:

NO PATH
NO PATH
5
0
-95
-100

代码模板

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;

const int N = 2e6 + 10, M = 1e5 + 10, INF = 0x3f3f3f3f;
int n,m;
int h[M],rh[N],ne[N],e[N],w[N],idx;

int buy[N],sell[N];
bool st[N];
void add(int *h, int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

//买入的最小值
void spfa1()
{
	queue<int> q;
	memset(buy, INF, sizeof buy);
	memset(st, false, sizeof st);
	q.push(1);
	while(q.size())
	{
		int t = q.front();
		q.pop();
		
		st[t] = false;
		for(int i = h[t]; ~i; i = ne[i] )
		{
			int j = e[i];
			if(buy[j] > min(buy[t], w[j]))
			{
				buy[j] = min(buy[t], w[j]);
				if(!st[j])
				{
					st[j] = true;
					q.push(j);					
				}				
			}
		
		}
	} 
	
}
//卖出的最大值 
void spfa2()
{
	queue<int> q;
	memset(st, false, sizeof st);
	q.push(n);
	
	while(q.size())
	{
		int t = q.front();
		q.pop();
		st[t] = false;
		for(int i = rh[t]; ~i; i = ne[i] )
		{
			int j = e[i];
			if(sell[j] < max(sell[t],w[j]))
			{
				sell[j] = max(sell[t],w[j]);
				if(!st[j])
				{
					st[j] = true;
					q.push(j);					
				}					
			}
	
		}
	} 
	
}

int main()
{
	cin >> n >> m;
	for(int i = 1; i <= n; i ++ ) cin >> w[i];
    memset(h, -1, sizeof h);
    memset(rh, -1, sizeof rh);
	while(m --)
	{
		int a,b,op;
		cin >> a >> b >> op;
        add(h, a, b), add(rh, b, a);
        if (op == 2) add(h, b, a), add(rh, a, b);
	}
	
	spfa1();
	spfa2();

	int ans = 0;
	for(int i = 1; i <= n; i ++ )
	{
		ans = max(ans, sell[i] - buy[i]);
	}
	cout << ans << endl;
	
	return 0;
}

 

单源最短路的扩展应用

1131. 拯救大兵瑞恩(bfs + 状态压缩)

在这里插入图片描述

输入样例:

4 4 9
9
1 2 1 3 2
1 2 2 2 0
2 1 2 2 0
2 1 3 1 0 
2 3 3 3 0
2 4 3 4 1
3 2 3 3 0
3 3 4 3 0
4 3 4 4 0
2
2 1 2 
4 2 1

输出样例:

14

代码模板

//邻接表 + 双端队列
#include <cstring>
#include <iostream>
#include <algorithm>
#include <deque>
#include <set>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 11, M = 360, P = 1 << 10;

int n, m, k, p;
int h[N * N], e[M], w[M], ne[M], idx;
int g[N][N], key[N * N];
int dist[N * N][P];
bool st[N * N][P];

set<PII> edges;

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

void build()
{
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            for (int u = 0; u < 4; u ++ )
            {
                int x = i + dx[u], y = j + dy[u];
                if (!x || x > n || !y || y > m) continue;
                int a = g[i][j], b = g[x][y];
                if (!edges.count({a, b})) add(a, b, 0);
            }
}

int bfs()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1][0] = 0;

    deque<PII> q;
    q.push_back({1, 0});

    while (q.size())
    {
        PII t = q.front();
        q.pop_front();

        if (st[t.x][t.y]) continue;
        st[t.x][t.y] = true;

        if (t.x == n * m) return dist[t.x][t.y];

        if (key[t.x])
        {
            int state = t.y | key[t.x];
            if (dist[t.x][state] > dist[t.x][t.y])
            {
                dist[t.x][state] = dist[t.x][t.y];
                q.push_front({t.x, state});
            }
        }

        for (int i = h[t.x]; ~i; i = ne[i])
        {
            int j = e[i];
            if (w[i] && !(t.y >> w[i] - 1 & 1)) continue;   // 有门并且没有钥匙
            if (dist[j][t.y] > dist[t.x][t.y] + 1)
            {
                dist[j][t.y] = dist[t.x][t.y] + 1;
                q.push_back({j, t.y});
            }
        }
    }

    return -1;
}

int main()
{
    cin >> n >> m >> p >> k;

    for (int i = 1, t = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            g[i][j] = t ++ ;

    memset(h, -1, sizeof h);
    while (k -- )
    {
        int x1, y1, x2, y2, c;
        cin >> x1 >> y1 >> x2 >> y2 >> c;
        int a = g[x1][y1], b = g[x2][y2];

        edges.insert({a, b}), edges.insert({b, a});
        if (c) add(a, b, c), add(b, a, c);
    }

    build();

    int s;
    cin >> s;
    while (s -- )
    {
        int x, y, c;
        cin >> x >> y >> c;
        key[g[x][y]] |= 1 << c - 1;
    }

    cout << bfs() << endl;

    return 0;
}

//邻接矩阵 + bfs
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int N = 105,M = 12;
typedef pair<int,int> PII;
int g[N][N],key[N],d[N][1<<M];
bool st[N][1<<M];
int n,m,p,k,s;
int dx[] = {0,1,0,-1};
int dy[] = {1,0,-1,0};
queue<PII> q;
int get(int x,int y){
    return (x - 1) * m + y;
}
int bfs(){
    int t = get(1,1);
    q.push({t,key[t]});
    st[t][key[t]] = true;
    memset(d,0x3f,sizeof d);
    d[t][key[t]] = 0;
    while(q.size()){
        PII u = q.front();
        q.pop();
        int z = u.first,v = u.second;
        for(int i = 0;i < 4;i++){
            int x = (z - 1) / m + dx[i] + 1,y = (z - 1) % m + dy[i] + 1;
            int v1 = v,z1 = get(x,y);
            if(!x || !y || x > n || y > m || !g[z][z1]) continue;
            if(g[z][z1] != -1){
                if(!(v >> g[z][z1] & 1))    continue;
            }
            v1 |= key[z1];
            if(!st[z1][v1]){
                q.push({z1,v1});
                st[z1][v1] = true;
                d[z1][v1] = d[z][v] + 1;
            } 
            if(z1 == n * m) return d[z1][v1];
        }
    }
    return -1;
}
int main(){
    cin>>n>>m>>p;
    cin>>k;
    int x1,y1,x2,y2,z,z1,z2;
    memset(g,-1,sizeof g);
    while(k--){
        cin>>x1>>y1>>x2>>y2>>z;
        z1 = get(x1,y1),z2 = get(x2,y2);
        g[z1][z2] = g[z2][z1] = z;
    }
    cin>>s;
    while(s--){
        cin>>x1>>y1>>z;
        key[get(x1,y1)] |= 1 << z;
    }
    cout<<bfs()<<endl;
    return 0;
}

 

1134. 最短路计数(最大值方案数)

在这里插入图片描述
思路
求条数的题目中一定不存在和小于等于0的环
将能更新前驱连起来 - > 拓扑序
bfs 每次扩展一层 -> 拓扑序
dijkstra 每个点只出队一次,且出队该点的cnt必然已经确定,即所有dist等于该点的前驱已经出队 -> 拓扑序
spfa 每个点可能入队出队多次不能保证拓扑序
例如:
在这里插入图片描述
思路
dijkstra 基于贪心策略,更新到6时候必然是由前驱5和3更新,7未被更新,6出队已经把所有min的前驱都更新完毕,cnt已经确定,即满足拓扑序
spfa 基于bellman-ford算法,6先被3更新,然后cnt还未确定就更新7,在等5更新6时候由于最小值相同不入队,7的cnt便无法更新,不满足拓扑序
本题由于边权都为1直接用bfs即可
输入样例:

5 7
1 2
1 3
2 4
3 4
2 3
4 5
4 5

输出样例:

1
1
1
2
4

代码模板

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010, M = 400010, mod = 100003;

int n, m;
int h[N], e[M], ne[M], idx;
int dist[N], cnt[N];
int q[N];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void bfs()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    cnt[1] = 1;

    int hh = 0, tt = 0;
    q[0] = 1;

    while (hh <= tt)
    {
        int t = q[hh ++ ];

        for (int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t] + 1) //更新最大值
            {
                dist[j] = dist[t] + 1;
                cnt[j] = cnt[t];  //当前得方案数 = 上结点得方案数
                q[ ++ tt] = j;
            }
            else if (dist[j] == dist[t] + 1) //最大值相等-> 对方案数操作
            {
                cnt[j] = (cnt[j] + cnt[t]) % mod; //当前得方案数 + 上一节点得方案数
            }
        }
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);

    while (m -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b), add(b, a);
    }

    bfs();

    for (int i = 1; i <= n; i ++ ) printf("%d\n", cnt[i]);

    return 0;
}

 

383. 观光(拆点dp求方案数)

在这里插入图片描述
思路
1.当前最短路变成次短路,更新最短路,将最短路和次短路加入优先队列,到达j的最短路个数和到达t是一样的
2.找到一条新的最短路,更新最短路条数,到达j的最短路个数应该加上到达t的最短路个数,从t经过的最短路,在j上经过的时候也是最短路
3.找到一条更短的次短路,覆盖掉当前次短路,加入优先队列,到达j的最短路个数和到达t是一样的
4.找到一条新的次短路,更新次短路条数,到达j的最短路个数应该加上到达t的最短路个数,从t经过的最短路,在j上经过的时候也是最短路

输入样例:

2
5 8
1 2 3
1 3 2
1 4 5
2 3 1
2 5 3
3 4 2
3 5 4
4 5 3
1 5
5 6
2 3 1
3 2 1
3 1 10
4 5 2
5 2 7
5 2 7
4 1

输出样例:

3
2

代码模板

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<queue>
using namespace std;
const int N = 1010,M = 10010;
int h[N],e[M],ne[M],w[M],idx;
//状态0表示的是求最下,状态1表示求的是次小
int cnt[N][2];  //cnt[i][0]表示当前到达节点是i,且求的是0状态下的所有路径中最短路径的边数
int dist[N][2]; //dist[i][0]表示当前到达节点是i,且求的是0状态下的最短路径的值
bool st[N][2];  //与上面同理
int n,m,S,T;  //S表示起点,T表示终点

struct node{   //小根堆,重载大于号
    int id,type,distance;   //分别是编号,状态,和当前点到起点的最小或次小距离
    bool operator> (const node& a) const{  //从大到小排序
        return distance > a.distance;
    }
};


void add(int a,int b,int c){
    w[idx] = c;
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

int dijkstra(){

    memset(st, 0, sizeof st);
    memset(dist, 0x3f, sizeof dist);
    memset(cnt, 0, sizeof cnt);

    priority_queue<node,vector<node>,greater<node>> heap;
    dist[S][0] = 0;
    cnt[S][0] = 1;
    heap.push({S,0,0});

    while(heap.size()){

        node t = heap.top();
        heap.pop();

        int ver = t.id , type = t.type , distance = t.distance; 
        if(st[ver][type]) continue;
        st[ver][type] = true;


        for(int i = h[ver];i != -1;i = ne[i]){
            int j = e[i];

            //先考虑最短的情况(大于、等于)
            if(dist[j][0] > dist[ver][type] + w[i]){
                //dist[j][0]成为次小,先要赋值给dist[j][]中次小的状态
                dist[j][1] = dist[j][0]; cnt[j][1] = cnt[j][0];
                heap.push({j, 1, dist[j][1]});  //发生改变就要入队

                dist[j][0] = dist[ver][type] + w[i];  cnt[j][0] = cnt[ver][type];  //直接转移
                heap.push({j,0,dist[j][0]});

            }else if(dist[j][0] == dist[ver][type] + w[i]){  

                cnt[j][0] += cnt[ver][type]; //从t经过的最短路,在j上经过的时候也是最短路

            //轮到枚举次小
            }else if(dist[j][1] > dist[ver][type] + w[i]){

                dist[j][1] = dist[ver][type] + w[i];
                cnt[j][1] = cnt[ver][type];
                 heap.push({j, 1, dist[j][1]});

            }else if(dist[j][1] == dist[ver][type] + w[i]){
                cnt[j][1] += cnt[ver][type];  //从t经过的最短路,在j上经过的时候也是最短路
            } 
        }
    }
    int res = cnt[T][0];
    //最后还要特判以下最小和次小的路径之间是否相差1符合要求
    if (dist[T][0] + 1 == dist[T][1]) res += cnt[T][1];  
    return res;
}


int main(){
    int t;
    cin >> t;
    while(t--){
        memset(h,-1,sizeof h);
        cin >> n >> m;
        for(int i = 0;i < m;++i){
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,c);
        }
        scanf("%d%d",&S,&T);
        cout << dijkstra() << endl;
    }
    return 0;
}

 

Floyd

1125. 牛的旅行(基本应用)

在这里插入图片描述
在这里插入图片描述
思路
1.floyd算法求出任意两个连通点之间的距离
2.求出所有连通集合中的点到该点最远的距离
3.枚举所有非连通点maxd[i] + maxd[j] + dist[i,j] :
即i点在该连通集合中最远的距离 + j点在该连通集合中最远的距离 + i两点的j的实际距离

输入样例:

8
10 10
15 10
20 10
15 15
20 15
30 15
25 10
30 10
01000000
10111000
01001000
01001000
01110000
00000010
00000101
00000010

输出样例:

22.071068

代码模板

#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>

#define x first
#define y second

using namespace std;

typedef pair<double, double> PDD;

const int N = 155;
const double INF = 1e20;

int n;
PDD q[N];
double d[N][N];
double maxd[N];
char g[N][N];

double get_dist(PDD a, PDD b)
{
    double dx = a.x - b.x;
    double dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
}

int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++ ) cin >> q[i].x >> q[i].y;
    for (int i = 0; i < n; i ++ ) cin >> g[i];

    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            if (i == j) d[i][j] = 0;
            else if (g[i][j] == '1') d[i][j] = get_dist(q[i], q[j]);
            else d[i][j] = INF;

    for (int k = 0; k < n; k ++ )
        for (int i = 0; i < n; i ++ )
            for (int j = 0; j < n; j ++ )
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);

    double r1 = 0;
    for (int i = 0; i < n; i ++ )
    {
        for (int j = 0; j < n; j ++ )
            if (d[i][j] < INF / 2)
                maxd[i] = max(maxd[i], d[i][j]);
        r1 = max(r1, maxd[i]);
    }

    double r2 = INF;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            if (d[i][j] > INF / 2)
                r2 = min(r2, maxd[i] + maxd[j] + get_dist(q[i], q[j]));

    printf("%.6lf\n", max(r1, r2));

    return 0;
}

 

343. 排序(传递闭包)

在这里插入图片描述
思路
从性质出发解决问题
解法1:根据传递闭包的性质出发 : 所有间接可以连接的边都手动添加一条边直接连接,用Floyd解决
矛盾 : 存在环,即dist[i,i] == 0
无法确定关系: 即dist[i,j] == 0 && dist[j,i] == 0;
可以确定关系:除上述条件外
如何输出拓扑序
除了已经确定点,当该点没有点可以直接连接该点,那么该点即为~
解法2:根据拓扑排序的性质解决
矛盾 : 存在环,导致队列为空时候还有入度为大于0的未入队
无法确定关系:入队的个数超过1
可以确定关系:除上述条件外

输入样例:

4 6
A<B
A<C
B<C
C<D
B<D
A<B
3 2
A<B
B<A
26 1
A<Z
0 0

输出样例:

Sorted sequence determined after 4 relations: ABCD.
Inconsistency found after 2 relations.
Sorted sequence cannot be determined.

代码模板1Floyd

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 26;

int n, m;
bool g[N][N], d[N][N];
bool st[N];

void floyd()
{
    memcpy(d, g, sizeof d);

    for (int k = 0; k < n; k ++ )
        for (int i = 0; i < n; i ++ )
            for (int j = 0; j < n; j ++ )
                d[i][j] |= d[i][k] && d[k][j];
}

int check()
{
    //是否矛盾
    for (int i = 0; i < n; i ++ )
        if (d[i][i])
            return 2;
    //能否确定关系
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < i; j ++ )
            if (!d[i][j] && !d[j][i])
                return 0;
    
    return 1;
}

char get_min()
{
    for (int i = 0; i < n; i ++ )
        if (!st[i])
        {
            bool flag = true;
            for (int j = 0; j < n; j ++ )
                if (!st[j] && d[j][i])
                {
                    flag = false;
                    break;
                }
            if (flag)
            {
                st[i] = true;
                return 'A' + i;
            }
        }
}

int main()
{
    while (cin >> n >> m, n || m)
    {
        memset(g, 0, sizeof g);
        int type = 0, t;  //1 可以确定关系 0不可以确定关系 2矛盾
        for (int i = 1; i <= m; i ++ )
        {
            char str[5];
            cin >> str;
            int a = str[0] - 'A', b = str[2] - 'A';

            if (!type)  //如果已经矛盾或者已经可以确定关系可以continue
            {
                g[a][b] = 1;
                floyd();
                type = check();
                if (type) t = i;
            }
        }

        if (!type) puts("Sorted sequence cannot be determined.");
        else if (type == 2) printf("Inconsistency found after %d relations.\n", t);
        else
        {
            memset(st, 0, sizeof st);
            printf("Sorted sequence determined after %d relations: ", t);
            for (int i = 0; i < n; i ++ ) printf("%c", get_min());
            printf(".\n");
        }
    }

    return 0;
}

代码模板2拓扑排序

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string> 
using namespace std;

const int N = 30, M = 1e4;
int h[N],ne[M],e[M],idx;
int id[N],dist[N];
bool st[N][N],s[N];
int n,m,type,cnt;
// 0 无法确定关系 1 矛盾 2 可以确定关系
void add(int a,int b)
{
    e[idx] = b,ne[idx] = h[a],h[a] = idx ++;
}

void topsort(int x,int d[])
{
    string res;
    bool flag = false;
    queue<int> q;
    for(int i = 0; i < n; i ++ )
        if(d[i] == 0 )
            q.push(i);
            
    while(q.size())
    {
        if(q.size() > 1)
        {
            type  = 0; 
            flag = true;
            //无法确定关系
        }
        int t = q.front();
        q.pop();
        res += (t + 'A');
        for(int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i];
            d[j]--;
            if(d[j] == 0) q.push(j);
        }
        
    }
    if(res.length() == n && !flag)  //可以确定关系
    {
        type = 2;
        cout << "Sorted sequence determined after " << x << " relations: " << res << ".\n";
    }
    if(res.length() < cnt) //矛盾
    {
        type = 1;
        printf("Inconsistency found after %d relations.\n", x);
    }
}

int main()
{
    while(cin >> n >> m, n || m)
    {
        char str[5];
        memset(h, -1, sizeof h);
        memset(id, 0, sizeof id);
        memset(st, false, sizeof st);
        memset(s,false,sizeof s);
        idx = 0;type = 0,cnt = 0;
        for(int i = 1; i <= m; i ++ )
        {
            cin >> str;
            int x = str[0] - 'A', y = str[2] - 'A';
            add(x,y),st[x][y] = true;
            id[y] ++;
            if(!s[x]) cnt ++,s[x] = true;
            if(!s[y]) cnt ++,s[y] = true;   
            memcpy(dist,id,sizeof dist);
            if(!type) topsort(i,dist);           
        }
        
        if(!type) printf("Sorted sequence cannot be determined.\n");
    }
    return 0;
}

 

344. 观光之旅(最小环)

在这里插入图片描述
思路
在这里插入图片描述

如图所示:k从小到大枚举,当枚举到k = 5时,1-4的最短路径为9,此时还未更新1-4经过5的最短路径 所以可以求出该环的大小即 1-4最短路径 + 1-5的边 + 5-4的边,以此类推... 记录路径:i < j < k ,i - j 的最短路径经过的范围在[i ,k - 1]中,每次记录i - j 由哪个k更新,最后先加入最大值k在加入最小值i,通过递归确定j和i - j中的k
1.无向图正常的Floyd会存在自环,会改变dist,所以需要增加 i!=j 的条件就不存在一个点的环
2.给的边没有自环,当k = i or k = j 时 为INF,所以不会存在两个点的环
3. 2 * INF < int, 3 * INF > int ; 3个INF 会溢出,所以需要开long long
4. 输出的顺序需要注意i k j -> i,按照环的顺序输出即可

输入样例:

5 7
1 4 1
1 3 300
3 1 10
1 2 16
2 3 100
2 5 15
5 3 20

输出样例:

1 3 5 2

代码模板

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

int n, m;
int d[N][N], g[N][N];
int pos[N][N];
int path[N], cnt;

void get_path(int i, int j)
{
    if (pos[i][j] == 0) return;

    int k = pos[i][j];
    get_path(i, k);
    path[cnt ++ ] = k;
    get_path(k, j);
}

int main()
{
    cin >> n >> m;

    memset(g, 0x3f, sizeof g);
    for (int i = 1; i <= n; i ++ ) g[i][i] = 0;

    while (m -- )
    {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b], c);
    }

    int res = INF;
    memcpy(d, g, sizeof d);
    for (int k = 1; k <= n; k ++ )
    {
        for (int i = 1; i < k; i ++ )
            for (int j = i + 1; j < k; j ++ )
                if ((long long)d[i][j] + g[j][k] + g[k][i] < res)
                {
                    res = d[i][j] + g[j][k] + g[k][i];
                    cnt = 0;
                    path[cnt ++ ] = k;
                    path[cnt ++ ] = i;
                    get_path(i, j);
                    path[cnt ++ ] = j;
                }

        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                if (d[i][j] > d[i][k] + d[k][j])
                {
                    d[i][j] = d[i][k] + d[k][j];
                    pos[i][j] = k;
                }
    }

    if (res == INF) puts("No solution.");
    else
    {
        for (int i = 0; i < cnt; i ++ ) cout << path[i] << ' ';
        cout << endl;
    }

    return 0;
}

 

345. 牛站

在这里插入图片描述

输入样例:

2 6 6 4
11 4 6
4 4 8
8 4 9
6 6 8
2 6 9
3 8 9

输出样例:

10

代码模板

#include <cstring>
#include <iostream>
#include <algorithm>
#include <map>

using namespace std;

const int N = 210;

int k, n, m, S, E;
int g[N][N];
int res[N][N];

void mul(int c[][N], int a[][N], int b[][N])
{
    static int temp[N][N];
    memset(temp, 0x3f, sizeof temp);
    for (int k = 1; k <= n; k ++ )
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                temp[i][j] = min(temp[i][j], a[i][k] + b[k][j]);
    memcpy(c, temp, sizeof temp);
}

void qmi()
{
    memset(res, 0x3f, sizeof res);
    for (int i = 1; i <= n; i ++ ) res[i][i] = 0;

    while (k)
    {
        if (k & 1) mul(res, res, g);    // res = res * g
        mul(g, g, g);   // g = g * g
        k >>= 1;
    }
}

int main()
{
    cin >> k >> m >> S >> E;

    memset(g, 0x3f, sizeof g);
    map<int, int> ids;
    if (!ids.count(S)) ids[S] = ++ n;
    if (!ids.count(E)) ids[E] = ++ n;
    S = ids[S], E = ids[E];

    while (m -- )
    {
        int a, b, c;
        cin >> c >> a >> b;
        if (!ids.count(a)) ids[a] = ++ n;
        if (!ids.count(b)) ids[b] = ++ n;
        a = ids[a], b = ids[b];

        g[a][b] = g[b][a] = min(g[a][b], c);
    }

    qmi();

    cout << res[S][E] << endl;

    return 0;
}

 

posted @ 2022-03-22 14:53  panse·  阅读(80)  评论(0)    收藏  举报