文章目录
新年好(最短路+dfs)
问题描述
解题思路
本题是在最短路的基础上添加了一个dfs
。由于有五个亲戚,因此我们可以先求出五个亲戚到其他各个点的最短路。再通过枚举搜索顺序,将拜访五个亲戚的顺序枚举一下,然后求出从一个亲戚走到另一个亲戚的最短路以此类推。
AC代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 50005, M = 2e5;
int n, m;
int mp[6];
int h[N], ne[M], w[M], e[M], idx;
int dist[6][N];//dist[i][j]表示的是当前从第i个亲戚家出发,到达点j的最短路
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);
memset(st, 0, sizeof st);
priority_queue<PII, vector<PII>, greater<PII>> heap;
dist[start] = 0;
heap.push({dist[start], 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 s)
{
if (u > 5)
return s;
int res = 1e9;
for (int i = 1; i <= 5; i++)
{
if (st[i])
continue;
st[i] = true;
res = min(res, dfs(u + 1, i, s + dist[start][mp[i]]));
st[i] = false;
}
return res;
}
int main()
{
cin >> n >> m;
mp[0] = 1;
for (int i = 1; i <= 5; i++)
cin >> mp[i];
memset(h, -1, sizeof h);
while (m--)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
add(b, a, c);
}
for (int i = 0; i <= 5; i++)
{
dijkstra(mp[i], dist[i]);
}
memset(st, 0, sizeof st);
cout << dfs(1, 0, 0) << endl;
return 0;
}
通信线路(最短路+二分)
问题描述
解题思路
题意:从1到n的路径当中,权值第k+1大的边的最小权值。
我们不难看出,可以二分得出答案:
1.对于答案x,需要满足的性质就是从1走到n需要经过大于x的边数小于等于k,作为分界点,此时大于xx的边数应该等于k。
2.对于答案右边的区间,x变大了,大于x的边数就会减小,即大于x的边数小于k,同样满足性质;
2.对于答案左边的区间,x变小了,大于x的边数就会增加,即大于x的边数大于k,不满足性质。
然后将图中所有大于bound的边权值置为1,小于等于的置为0。
而最终dist[n]
表示的就是有多少条路径长度超过了bound
AC代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1010, M = 20005;
int n, m, k;
int dist[N];
int h[N], e[M], ne[M], w[M], idx;
deque<int> q;
bool st[N];
void add(int a, int b, int c) // 添加一条边a->b,边权为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)
r = -1;
cout << r << endl;
return 0;
}
道路与航线(最短路+拓扑排序)
问题描述
解题思想
图中的边分为两部分:道路
与航线
道路全部都是正权边,航线有正有负。同时,航线一定不是双向的并且无法回到原来的点,由此可以推断出航线一定组成了一个拓扑序列。
因此,可以现在各个连通块内跑一遍迪杰斯特拉,然后利用拓扑序列逐个更新完每个连通块然后求出最小值。
AC代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 30000, M = 150010, INF = 0x3f3f3f3f;
int n, S, mr, mp;
int h[N], e[M], w[M], ne[M], idx;
int dist[N];
bool st[N];
int id[N]; //表示了每个连通块的编号
int bcnt; //代表连通块的数量
int deg[N]; //连通块的入度数组
vector<int> block[N]; //一个block[i]代表一个连通块,其内部元素代表了连通块的编号
queue<int> q;
void add(int a, int b, int c) // 添加一条边a->b,边权为c
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
// dfs用于确定连通块的数量以及存储各连通块
void dfs(int u)
{
block[bcnt].push_back(u);
id[u] = bcnt;
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (!id[j])
dfs(j);
}
}
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;
if (st[ver])
continue;
st[ver] = true;
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
//如果当前点j和ver不是一个连通块,那么让j所在连通块入度减1
//如果当前j所在连通块入度减为0了,那么进入拓扑序列
if (id[j] != id[ver] && --deg[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;
//先将所有入度为0的点加入拓扑序列
for (int i = 1; i <= bcnt; i++)
{
if (!deg[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);
//在点之间建立道路
for (int i = 0; i < mr; i++)
{
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);
}
}
//在连通块之间建立航线
for (int i = 0; i < mp; i++)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
deg[id[b]]++; // b所在的连通块入度+1
}
topsort();
for (int i = 1; i <= n; i++)
{
if (dist[i] > INF / 2)
puts("NO PATH");
else
cout << dist[i] << endl;
}
return 0;
}
最优贸易(最短路+dp思想)
问题描述
解题思路
从 1 走到 i 的过程中,买入水晶球的最低价格dmin[i]
;
从 i 走到 n 的过程中,卖出水晶球的最高价格 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 算法。
AC代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 100005, M = 1500000;
int n, m;
int w[N];
int rh[N], h[N], e[M], ne[M], idx;
int dmin[N], dmax[N];
queue<int> q;
bool st[N];
void add(int h[], int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void spfa(int h[], int dist[], int type)
{
if (type == 1)
{
memset(dist, 0x3f, sizeof dmin);
dist[1] = w[1];
q.push(1);
}
else
{
memset(dist, -0x3f, sizeof dmax);
dist[n] = w[n];
q.push(n);
}
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 (type == 1 && dist[j] > min(dist[t], w[j]) || type == 2 && dist[j] < max(dist[t], w[j]))
{
if (type == 1)
dist[j] = min(dist[t], w[j]);
else
dist[j] = max(dist[t], w[j]);
if (!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
}
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, x;
cin >> a >> b >> x;
add(h, a, b), add(rh, b, a);
if (x == 2)
add(h, b, a), add(rh, a, b);
}
spfa(h, dmin, 1);
spfa(rh, dmax, 2);
int res = 0;
for (int i = 1; i <= n; i++)
res = max(res, dmax[i] - dmin[i]);
cout << res;
return 0;
}