AcWing 1127 香甜的黄油
题目传送门
算法分析
枚举所有点作为特定牧场,求特定牧场到所有点的最短距离
点的个数\(n= 800\),边的个数\(m=1500\)
朴素版\(Dijkstra\) 复杂度是\(O(n^3)\) \(n^3=5.12?10^8\) 垃圾中的战斗机,不用背代码了~
堆优化版\(dijkstra\) 复杂度是\(O(n\times m \times log_n)\) \(n \times m \times log_n=1.2?10^7\)
\(spfa\) 复杂度是\(O(nm)\) 平均是\(2\)到\(3\)倍即 \(3 \times n \times m\)=\(3.9?10^6\)
\(spfa\)好强,时间复杂度 \(O(nm)\),最坏\(O(n^2m)\) 但这玩意太玄学,如果出题人故意卡你一下,就可能退化到\(O(n^2\times m)\)垃圾的要死,太不稳定。
一般用于求带负权边的最短路,全是正权边的还是用堆优化的\(Dijkstra\)吧:
一、SPFA
#include
using namespace std;
typedef pair PII;
const int N = 810; //牧场数 上限800
const int M = 3000; //牧场间道路数 上限1450,无向图开双倍
const int INF = 0x3f3f3f3f;
//链式前向星
int h[N], e[M], w[M], ne[M], idx;
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int n, p, m; //三个数:奶牛数 ,牧场数 ,牧场间道路数
int id[N]; //每只奶牛在哪个牧场
int dist[N]; //记录起点到任意点的最短路径
int q[N]; // spfa用到的队列
bool st[N]; //标识每个牧场是否入过队列
/*
该算法常用于求含负权图的单源最短路,是Bellman_ford算法的优化版。
如果a点到终点途中经过b点,那么只有dist[a]更新变小后,dist[b]更新之后才有可能变小。
所以spfa算法相较于Bellman-ford算法优化的点就是:只有x的邻点的最短路被更新变小之后,x的最短路才需要进行更新判断,操作通过队列实现。
一般时间复杂度:O(m)。
最坏时间复杂度:O(n*m)。
其中n:点的个数,m:边的个数,如果出题人卡常数,那么SPFA会被卡掉。
*/
int spfa(int start) {
//多轮使用spfa,所以一定要清空
memset(st, 0, sizeof st);
memset(dist, 0x3f, sizeof dist);
dist[start] = 0;
//普通队列
queue q;
q.push(start);
st[start] = true; // st数组标记当前在队列中的点
while (q.size()) {
int u = q.front();
q.pop();
st[u] = false;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (dist[u] + w[i] < dist[j]) {
dist[j] = dist[u] + w[i];
if (!st[j]) {
q.push(j);
st[j] = true;
}
}
}
}
int res = 0;
for (int i = 1; i <= n; i++) {
if (dist[id[i]] == INF) return INF;
res += dist[id[i]];
}
return res;
}
int main() {
memset(h, -1, sizeof h);
cin >> n >> p >> m;
for (int i = 1; i <= n; i++) cin >> id[i];
while (m--) {
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
int ans = INF;
for (int i = 1; i <= p; i++) ans = min(ans, spfa(i));
cout << ans << endl;
return 0;
}
二、Dijkstra+PII
#include
using namespace std;
typedef pair PII;
const int N = 810; //牧场数 上限800
const int M = 3000; //牧场间道路数 上限1450,无向图开双倍
const int INF = 0x3f3f3f3f;
//链式前向星
int h[N], e[M], w[M], ne[M], idx;
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int n, p, m; //三个数:奶牛数 ,牧场数 ,牧场间道路数
int id[N]; //每只奶牛在哪个牧场
int dist[N]; //记录起点到任意点的最短路径
int q[N]; // spfa用到的队列
bool st[N]; //标识每个牧场是否入过队列
/*
Dijkstra算法是由荷兰神犇Dijkstra发明的最短路算法,是目前最快的寻路算法,
堆优化后时间复杂度可达O((m+n)logm),但缺点是不能处理负权边。
*/
int dijkstra(int start) {
memset(st, 0, sizeof st);
memset(dist, 0x3f, sizeof dist);
dist[start] = 0;
priority_queue, greater> q;
q.push({0, start});
while (q.size()) {
PII t = q.top();
q.pop();
int u = t.second, d = t.first;
if (!st[u]) {
st[u] = true;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (dist[j] > d + w[i]) {
dist[j] = d + w[i];
q.push({dist[j], j});
}
}
}
}
int res = 0;
for (int i = 1; i <= n; i++) {
int j = id[i];
if (dist[j] == INF) return INF;
res += dist[j];
}
return res;
}
int main() {
memset(h, -1, sizeof h);
cin >> n >> p >> m;
for (int i = 1; i <= n; i++) cin >> id[i];
while (m--) {
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
int ans = INF;
for (int i = 1; i <= p; i++) ans = min(ans, dijkstra(i));
cout << ans << endl;
return 0;
}
三、Dijkstra+Struct
#include
using namespace std;
const int N = 810; //牧场数 上限800
const int M = 3000; //牧场间道路数 上限1450,无向图开双倍
const int INF = 0x3f3f3f3f;
struct Node {
int dist;
int id;
};
bool operator<(const Node &a, const Node &b) {
return a.dist > b.dist;
}
//链式前向星
int h[N],
e[M], w[M], ne[M], idx;
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int n, p, m; //三个数:奶牛数 ,牧场数 ,牧场间道路数
int id[N]; //每只奶牛在哪个牧场
int dist[N]; //记录起点到任意点的最短路径
int q[N]; // spfa用到的队列
bool st[N]; //标识每个牧场是否入过队列
int dijkstra(int start) {
//因为调用多次,需要每次初始化
memset(st, 0, sizeof st);
memset(dist, 0x3f, sizeof dist);
dist[start] = 0;
priority_queue q;
q.push({0, start});
while (q.size()) {
auto t = q.top();
q.pop();
int u = t.id, d = t.dist;
if (!st[u]) {
st[u] = true;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (dist[j] > d + w[i]) {
dist[j] = d + w[i];
q.push({dist[j], j});
}
}
}
}
int res = 0;
for (int i = 1; i <= n; i++) {
int j = id[i];
if (dist[j] == INF) return INF;
res += dist[j];
}
return res;
}
int main() {
//用来装图的链表头数组,只需初始化一次
memset(h, -1, sizeof h);
cin >> n >> p >> m;
for (int i = 1; i <= n; i++) cin >> id[i];
while (m--) {
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
int ans = INF;
for (int i = 1; i <= p; i++) ans = min(ans, dijkstra(i));
cout << ans << endl;
return 0;
}