1.选择最佳路线(多个起点一个终点)
思路:这道题目有一个特点,就是有多个起点。
我们可以建立一个虚拟的原点,把原点向所有起点连一条边,价值为0,这样就不会影响答案。
所以,直接以虚拟原点为起点,做一遍spfa。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include<bits/stdc++.h>
using namespace std;
const int N = 1010,M=40010;
int n,m,T;
int h[N], e[M], w[M], ne[M], idx;
int q[N], dist[N];
bool st[N];
void add(int a,int b,int c)//加边操作
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int spfa() //求最短路
{
memset(dist, 0x3f, sizeof dist);
dist[0] = 0;
int hh = 0, tt = 1;
q[0] = 0;
while (hh != tt)
{
int t = q[hh ++ ];
if (hh == N) hh = 0;
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
if (!st[j])
{
q[tt ++ ] = j;
if (tt == N) tt = 0;
st[j] = true;
}
}
}
}
if (dist[T] == 0x3f3f3f3f) return -1;
return dist[T];
}
int main()
{
while(scanf("%d%d%d",&n,&m,&T)!=-1)//有多组测试数据
{
memset(h,-1,sizeof h);
memset(st, 0, sizeof st);
idx=0;
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a, b, c);//原来就有的边
}
int s;
scanf("%d",&s);
while(s--)
{
int ver;
scanf("%d",&ver);//输入多个原点
add(0,ver,0);//建立价值为0的边
}
printf("%d\n",spfa());
}
return 0;
}
2.有钥匙走迷宫(dp思想)
题目分析
对于每个点,它们的属性不光有(x,y)
坐标这个属性,还有是否有钥匙这个属性,所以一个点(x,y),可以根据其属性来写为三维状态f(x,y,state),表示从起点到(x,y)
这个点且当前拥有个要是状态是state的路线最短路
看似可以用dp(动态规划来做这个题),但是用dp做,计算当前状态就需要之前的所有状态,但是这是一个网格状的棋盘,是可以走回路的
比如说,一个人在(x,y)先去拿钥匙,再回(x,y),出现环形结构,那么计算当前状态反而需要当前状态的值,出现矛盾,不太好计算
所以可以把f(x,y,state),当成一个三维坐标,但和正常的三维坐标不同的是,第三个状态不是位置,而是一种钥匙的状态,但不妨碍可以借鉴三维坐标。
先用dp的思想来想状态来如何转移key
来表示当前位置的钥匙存在状态
此时这是拿当前位置钥匙的方程转移f(x,y,state)=min(f(x,y,state),f(x,y,state|key)),拿钥匙不消耗时间
如果不拿钥匙,往四个方向走,需要消耗1个时间点
此时f(x,y,state)=min(f(a,b,state)+1,f(x,y,state))
需要的结构是拓扑序,导致不能正常转移有环形依赖的结构则需要最短路解决
通过上述的状态转移方程,发现当前位置有钥匙的话,捡起钥匙,不需要消耗体力,所以就相当于边权为0
如果不捡位置,取周围4个方向的位置(能走过去的话),则需要消耗1个体力,相当于边权为1
此时问题就简化为了,一个三维状态点,且边权是0和1的最短路问题
边权是0和1的可以用Spfa/双端队列广搜/Dijkstra
来做,双端队列BFS时间复杂度是线性的,这里题解代码就仿照y总,写了个双端BFS双端版
处理细节
1.降维:对于状态f(x,y,state)
可以把二维坐标(x,y)降维成一维,把状态简化成f(t.state)
2.钥匙状态表示:对于钥匙的状态,可以用二进制来表示,拥有那种钥匙,就是2的多少次方
例如,拥有1号钥匙,就是21,二进制下就是10状态状态state,拿起当前位置的钥匙就是或运算,state|=key
3.对于门和墙和普通通道的表示,可以用邻接表来表示点与点之间是什么门
先用set来存两个点之间的不同门,对于墙,标记一下,不用存,代表该边不可走
最后遍历其他所有的点对,看看是否在set里,不在的话,说明是普通通道,标记为普通就行
#include <bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
const int N = 11, M = 400, 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 dis[N * N][P];
bool vis[N * N][P];
set<PII> edge;
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
//没门没墙的就是普通通道
void build() {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
for (int k = 0; k < 4; k++) {
int x = i + dx[k];
int y = j + dy[k];
if (x < 1 || x > n || y < 1 || y > m) continue;
int a = g[i][j], b = g[x][y];
if (!edge.count({a, b})) {
add(a, b, 0);
}
}
}
}
}
//经典双端队列BFS处理01边权最短路
int bfs() {
memset(dis, 0x3f, sizeof(dis));
dis[1][0] = 0;
deque<PII> deq;
deq.push_back({1, 0});
while (deq.size()) {
PII t = deq.front();
deq.pop_front();
if (vis[t.x][t.y]) continue;
vis[t.x][t.y] = 1;
if (t.x == n * m) return dis[t.x][t.y];
//捡起钥匙的情况
if (key[t.x]) {
int state = t.y | key[t.x];
if (dis[t.x][state] > dis[t.x][t.y]) {
dis[t.x][state] = dis[t.x][t.y];
deq.push_front({t.x, state});
}
}
for (int i = h[t.x]; i != -1; i = ne[i]) {
int j = e[i];
//这是当前有门,但是没钥匙的情况
if (w[i] != 0 && !(t.y >> w[i] - 1 & 1)) continue;
if (dis[j][t.y] > dis[t.x][t.y] + 1) {
dis[j][t.y] = dis[t.x][t.y] + 1;
deq.push_back({j, t.y});
}
}
}
return -1;
}
int main() {
scanf("%d%d%d%d", &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, s;
scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &s);
int a = g[x1][y1], b = g[x2][y2];
edge.insert({a, b});
edge.insert({b, a});//点明此处是墙
if (s != 0) add(a, b, s), add(b, a, s);
}
//建立普通通道
build();
int s;
scanf("%d", &s);
//二进制数字表示钥匙状态
while (s--) {
int x, y, c;
scanf("%d%d%d", &x, &y, &c);
key[g[x][y]] |= 1 << c - 1;//地图存在钥匙时的状态
}
printf("%d\n", bfs());
return 0;
}
3.最短路计数
BFS做法
#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;
}
4.观光(求最短路和次短路)
#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;
}
作者:牛蛙点点
链接:https://www.acwing.com/solution/content/60646/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。