洛谷P3556 [POI2013] MOR-Tales of seafaring的三种解法
本题模板为奇偶最短路(边权为1时的),题目链接:https://www.luogu.com.cn/problem/P3556
为了研究,码了三种不同最短路解放的奇偶做法,便于不同群体理解.
一:BFS,对于边权为1,求最短路当然是BFS最快了,时间复杂度:o(nm),代码如下:
点击查看代码
//背景:我的BFS奇偶最短路尝试
//思路:对于边权为1的情况,毫无疑问,这是我们最开始学BFS时的应用,对于单源最短路边权为1的最优解就是BFS,同时可以证明边权为1时SPFA算法也取最优时间
//时间复杂度:o(nm)
#include <bits/stdc++.h>
using namespace std;
typedef struct situation
{
int to,d,id;
}SI;
vector<int> e[5001];
vector<SI> ask[5001];
bool ans[1000001];
int dis[5001][2];
void bfs(int b)//主要看这里
{
bool vis[5001][2];//奇偶标记数组
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
vis[b][0] = true;
dis[b][0] = 0;//奇偶答案
queue<pair<int,int>> que;
que.push({b,0});
while(!que.empty())
{
int x = que.front().first;
int y = que.front().second;
que.pop();
for (auto v:e[x])//漂亮的奇偶处理
{
if(vis[v][y^1]) continue;
dis[v][y^1] = dis[x][y] + 1;
vis[v][y^1] = true;
que.push({v,y^1});
}
}
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
memset(dis,0x3f,sizeof(dis));
const int INF = dis[0][0];
int n,m,q,x,y,d;
cin>>n>>m>>q;
for (int i = 1;i<=m;i++)
{
cin>>x>>y;
e[x].push_back(y);
e[y].push_back(x);
}
for (int i = 1;i<=q;i++)
{
cin>>x>>y>>d;
ask[x].push_back({y,d,i});
}
for (int i = 1;i<=n;i++)
{
if(ask[i].empty()) continue;
bfs(i);
for (auto v:ask[i])
{
x = v.to;
y = v.d;
int temp = v.id;
if(e[i].size() == 0) ans[temp] = false;
else
{
if(dis[x][y%2] == INF) ans[temp] = false;
else
{
if(dis[x][y%2] <= y) ans[temp] = true;
else ans[temp] = false;
}
}
}
}
for (int i = 1;i<=q;i++)
{
if(ans[i]) cout<<"TAK"<<"\n";
else cout<<"NIE"<<"\n";
}
return 0;
}
二:SPFA算法,不稳定算法,时间复杂度优则o(m),坏则o(nm),可以证明对于边权为1的情况,SPFA等价于BFS,故时间复杂度为o(nm),代码如下:
点击查看代码
//背景:超级恶心的题目,涉及奇偶最短路,且时间复杂度感觉过不了!且细节超级多
//收获:SPFA求奇偶最短路,简单路是没有重复节点的路,题目求的不是简单路,所以应该要奇偶处理,而我发现SPFA的奇偶处理比Dijkstra好处理
//原理:最短路 + 离线处理
//时间复杂度:最优o(nm),最坏(n^2*m),一般来说跑的比较快(听说的)
#include <bits/stdc++.h>
using namespace std;
typedef struct situation//离线处理结构体
{
int to,id,d;//to为另一端点,id为询问的编号,d为要求的长度
}SI;
int n,m,k;
int dis[5001][2];//如果不离线处理的话,开成全源最短路数组会MLE!这是为什么要离线处理的原因
vector<int> e[5001];//邻接表存图
void SPFA(int now)//跑奇偶SPFA算法
{
memset(dis,0x3f,sizeof(dis));
bool vis[n+1];
memset(vis,0,sizeof(vis));
dis[now][0] = 0;
queue<int> que;
que.push(now);
vis[now] = true;
while(!que.empty())
{
int x = que.front();
que.pop();
vis[x] = false;
for (auto v:e[x])
{
if(dis[v][1] > dis[x][0] + 1)//奇偶判断
{
dis[v][1] = dis[x][0] + 1;
if(vis[v] == false)
{
vis[v] = true;
que.push(v);
}
}
if(dis[v][0] > dis[x][1] + 1)//奇偶判断
{
dis[v][0] = dis[x][1] + 1;
if(vis[v] == false)
{
vis[v] = true;
que.push(v);
}
}
}
}
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
memset(dis,0x3f,sizeof(dis));
const int INF = dis[0][0];//获取无穷大
int x,y,d;
cin>>n>>m>>k;
vector<SI> ask[n+1];//离线询问数组
for (int i = 1;i<=m;i++)
{
cin>>x>>y;
e[x].push_back(y);
e[y].push_back(x);
}
bool ans[k+1];//记录对应询问编号的答案,注意这里数组大小是k+1
for (int i = 1;i<=k;i++)//离线处理
{
cin>>x>>y>>d;
ask[x].push_back({y,i,d});
}
for (int i = 1;i<=n;i++)//依次以每个点为源点进行最短路处理
{
if(ask[i].empty()) continue;//如果对应它没有询问,没必要跑最短路,跳过
SPFA(i);//跑SPFA最短路
for (auto v:ask[i])//遍历询问
{
if(e[i].size() == 0)//!!!!重中之重,如果是孤立点,一定无解,可能有疑惑,孤立点不就是没有路了吗?我下面的判INF操作明明可以处理啊
//事实上,对于询问自己到自己的最短路时(对于孤立点的情况)可以Hack掉我的判断,所以这个还是要有的
{
ans[v.id] = false;//记录
continue;//跳过
}
if(v.d%2 == 0)//如果是偶数
{
if(dis[v.to][0] == INF) ans[v.id] = false;//无路情况
else
{
if(dis[v.to][0] > v.d) ans[v.id] = false;//最短路大于询问,肯定不行
else ans[v.id] = true;
}
}
else//奇数同理
{
if(dis[v.to][1] == INF) ans[v.id] = false;
else
{
if(dis[v.to][1] > v.d) ans[v.id] = false;
else ans[v.id] = true;
}
}
}
}
for (int i = 1;i<=k;i++)//遍历询问
{
if(ans[i]) cout<<"TAK"<<"\n";//输出
else cout<<"NIE"<<"\n";
}
return 0;
}
//下面是漂亮版的奇偶dp
//细节:vis[][]开二维
void SPFA(int now)//跑奇偶SPFA算法
{
memset(dis,0x3f,sizeof(dis));
bool vis[n+1][2];
memset(vis,0,sizeof(vis));
dis[now][0] = 0;
queue<pair<int,int>> que;
que.push({now,0});
vis[now][0] = true;
while(!que.empty())
{
int x = que.front().first;
int y = que.front().second;
que.pop();
vis[x][y] = false;
for (auto v:e[x])
{
if(dis[v][y^1] > dis[x][y] + 1)
{
dis[v][y^1] = dis[x][y] + 1;
if(vis[v][y^1]) continue;
vis[v][y^1] = true;
que.push({v,y^1});
}
}
}
}
三,Dijkstra算法,稳定算法,时间复杂度为o(nmlogm),代码:
点击查看代码
//背景:当Dijkstra算法求奇偶最短路归位时,终于我的全部方法求奇偶最短路均已实现
//分析:对于边权为1的情况,显然是SPFA与BFS快,但是边权不为1,Dijkstra无敌,但是边权不为1的代码估计会难写
//时间复杂度:o(nmlogm)
#include <bits/stdc++.h>
using namespace std;
typedef struct situation
{
int to,d,id;
}SI;
typedef struct dij
{
int u,d,odd;//在原Dijkstra元素结构体的基础上加了奇偶判断
}DIJ;
struct cmp
{
bool operator()(DIJ x,DIJ y)
{
return x.d > y.d;
}
};
vector<int> e[5001];
vector<SI> ask[5001];
bool ans[1000001];
int dis[5001][2];
void Dijkstra(int b)//主要差异
{
bool vis[5001][2];
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));//这个初始化不要忘了
dis[b][0] = 0;//初始化
priority_queue<DIJ,vector<DIJ>,cmp> que;
que.push({b,0,0});//入队
while(!que.empty())
{
int x = que.top().u;
int odd = que.top().odd;
que.pop();//不要忘
if(vis[x][odd]) continue;
vis[x][odd] = true;
for (auto v:e[x])
{
if(dis[v][odd^1] > dis[x][odd] + 1)
{
dis[v][odd^1] = dis[x][odd] + 1;
que.push({v,dis[v][odd^1],odd^1});
}
}
}
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
memset(dis,0x3f,sizeof(dis));
const int INF = dis[0][0];
int n,m,q,x,y,d;
cin>>n>>m>>q;
for (int i = 1;i<=m;i++)
{
cin>>x>>y;
e[x].push_back(y);
e[y].push_back(x);
}
for (int i = 1;i<=q;i++)
{
cin>>x>>y>>d;
ask[x].push_back({y,d,i});
}
for (int i = 1;i<=n;i++)
{
if(ask[i].empty()) continue;
Dijkstra(i);
for (auto v:ask[i])
{
x = v.to;
y = v.d;
int temp = v.id;
if(e[i].size() == 0) ans[temp] = false;
else
{
if(dis[x][y%2] == INF) ans[temp] = false;
else
{
if(dis[x][y%2] <= y) ans[temp] = true;
else ans[temp] = false;
}
}
}
}
for (int i = 1;i<=q;i++)
{
if(ans[i]) cout<<"TAK"<<"\n";
else cout<<"NIE"<<"\n";
}
return 0;
}
总结:对于边权为1,优先BFS或者SPFA,否则Dijkstra
码字不易,多多支持