做了一下《训练指南》上的的最短路的例题,贴一下代码,就当是做个备忘录了。如果要看题解,书上写的挺详细的~~
下面的有些题意和分析是网上搜的,自己太懒了
还有有的题的代码是lrj的,他的代码有注释,读起来容易~~
题目:https://vjudge.net/contest/135918#overview
题目:UVa 11374
题意:
在Iokh市中,机场快线是市民从市内去机场的首选交通工具。机场快线分为经济线和商业线两种,线路,速度和价钱都不同。你有一张商业线车票,可以做一站商业线,而其他时候只能乘坐经济线。假设换乘时间忽略不计,你的任务是找一条去机场最快的路线。。
分析:
枚举商业线T(a,b),则总时间为d1(a)+T(a,b)+d2(b);d1和d2用两次dijkstra来计算,以S为起点的dijkstra和以E为起点的dijkstra;
代码:
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
#include <string>
#include <map>
#include <cmath>
#include <queue>
#include <set>
#include <stack>
using namespace std;
const int INF=1e8;
const int N = 500 + 10;
vector<int> path;
int d1[N],d2[N],f1[N],f2[N],w[N][N],vis[N];
int T,n,s,e,M,a,b,c;
void dij(int d[],int fa[],int s) {
memset(vis,0,sizeof(vis));
for(int i=1; i<=n; i++) {
int x,m=INF;
for(int j=1; j<=n; j++)if(!vis[j]&&d[j]<m)m=d[x=j];
vis[x]=1;
for(int j=1; j<=n; j++)if(!vis[j]&&d[j]>d[x]+w[x][j]) {
d[j]=d[x]+w[x][j];
fa[j]=x;
}
}
}
void print_ans1(int u) {
if(f1[u])print_ans1(f1[u]);
path.push_back(u);
}
void print_ans2(int u) {
path.push_back(u);
if(f2[u])print_ans2(f2[u]);
}
int main() {
//freopen("f.txt","r",stdin);
int cas=0;
while(~scanf("%d%d%d",&n,&s,&e)) {
if(cas)printf("\n");
++cas;
memset(f1,0,sizeof(f1));
memset(f2,0,sizeof(f2));
path.clear();
scanf("%d",&M);
for(int i=1; i<=n; i++)for(int j=1; j<=n; j++)w[i][j]=INF;
for(int i=0; i<M; i++) {
scanf("%d%d%d",&a,&b,&c);
if(c<w[a][b])
w[a][b]=w[b][a]=c;
}
for(int i=1; i<=n; i++)d1[i]=d2[i]=INF;
d1[s]=0;
dij(d1,f1,s);
d2[e]=0;
dij(d2,f2,e);
int ans=d1[e],p1=-1,p2=-1;
int K;
scanf("%d",&K);
while(K--) {
scanf("%d%d%d",&a,&b,&c);
if(d1[a]+c+d2[b]<ans) {
ans=d1[a]+c+d2[b];
p1=a;
p2=b;
}
if(d1[b]+c+d2[a]<ans) {
ans=d1[b]+c+d2[a];
p1=b;
p2=a;
}
}
if(p1==-1) {
print_ans1(e);
} else {
print_ans1(p1);
print_ans2(p2);
}
for(int i=0; i<path.size()-1; i++)printf("%d ",path[i]);
printf("%d\n",e);
if(p1==-1)puts("Ticket Not Used");
else printf("%d\n",p1);
printf("%d\n",ans);
}
return 0;
}
题目:UVa 10917
题意:
gbn最近打算穿过一个森林,但是他比较傲娇,于是他决定只走一些特殊的道路,他打算只沿着满足如下条件的(A,B)道路走:存在一条从B出发回家的路,比所有从A出发回家的路径都短。你的任务是计算一共有多少条不同的回家路径。其中起点的编号为1,终点的编号为2.
分析:
首先从终点Dijkstra一次,求出每个点u回家的最短路长度,那么相当于创建了一个新图,当d[B]< d[A]时有一条A指向B的有向边,则题目的目标就是求出起点到终点的路径条数。因为这个图是DAG,可以用动态规划求解。
代码:
using namespace std;
const int INF=0x3f3f3f3f;
const int N = 1000 + 10;
int w[N][N],f[N],d[N],vis[N],n,m,a,b,c;
void dij() {
memset(vis,0,sizeof(vis));
for(int i=1; i<=n; i++) {
int x,m=INF;
for(int j=1; j<=n; j++)if(!vis[j]&&d[j]<m)m=d[x=j];
vis[x]=1;
for(int j=1; j<=n; j++)if(!vis[j]&&d[j]>d[x]+w[x][j]) {
d[j]=d[x]+w[x][j];
}
}
}
int dp(int u) {
if(u==2)return 1;
int& ans=f[u];
if(ans>=0)return ans;
ans=0;
for(int i=1; i<=n; i++)
if(w[u][i]<INF&&d[i]<d[u])ans+=dp(i);
return ans;
}
int main() {
//freopen("f.txt","r",stdin);
while(~scanf("%d%d",&n,&m)&&n) {
memset(w,0x3f,sizeof(w));
memset(d,0x3f,sizeof(d));
for(int i=0; i<m; i++) {
scanf("%d%d%d",&a,&b,&c);
if(c<w[a][b])w[a][b]=w[b][a]=c;
}
d[2]=0;
dij();
memset(f,-1,sizeof(f));
printf("%d\n",dp(1));
}
}
题目:LA 4080
题意:
①先求任意两点间的最短路径累加和,其中不连通的边权为L ②删除任意一条边,求全局最短路径和的最大值。
分析:
第一个问题很好解决,floyd 和 n 次 dij 或者 spfa 都可以。关键是删掉一条边那里。如果枚举删掉的边,那复杂度就是 m*n^3 ,换 dij 和 spfa 应该也差不多,都过不了。此时就要想到,不是无论删除哪条边,所有东西都要重新算的,只有那些在最短路树上的边被删除时,才需要重新算。所以就是先 dij 处理出 n 个最短路树和每个为源点时的最短路和。然后就是上面的步骤了,先判断要不要重新算就好了。这里有个陷阱的,就是有重边。如果你要删除了两个点之间的一条边,那么肯定是删的最短的那一条,因为要使 c 最大么,删掉后不是就没有边了,应该是第二短的边顶上,所以这里还要稍微处理一下。
代码:
// LA4080/UVa1416 Warfare And Logistics
// Rujia Liu
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
const int INF = 1000000000;
const int maxn = 100 + 10;
struct Edge {
int from, to, dist;
};
struct HeapNode {
int d, u;
bool operator < (const HeapNode& rhs) const {
return d > rhs.d;
}
};
struct Dijkstra {
int n, m;
vector<Edge> edges;
vector<int> G[maxn];
bool done[maxn]; // 是否已永久标号
int d[maxn]; // s到各个点的距离
int p[maxn]; // 最短路中的上一条弧
void init(int n) {
this->n = n;
for(int i = 0; i < n; i++) G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int dist) {
edges.push_back((Edge) {
from, to, dist
});
m = edges.size();
G[from].push_back(m-1);
}
void dijkstra(int s) {
priority_queue<HeapNode> Q;
for(int i = 0; i < n; i++) d[i] = INF;
d[s] = 0;
memset(done, 0, sizeof(done));
Q.push((HeapNode) {
0, s
});
while(!Q.empty()) {
HeapNode x = Q.top();
Q.pop();
int u = x.u;
if(done[u]) continue;
done[u] = true;
for(int i = 0; i < G[u].size(); i++) {
Edge& e = edges[G[u][i]];
if(e.dist > 0 && d[e.to] > d[u] + e.dist) { // 此处和模板不同,忽略了dist=-1的边。此为删除标记。
//根据题意和dijkstra算法的前提,正常的边dist>0
d[e.to] = d[u] + e.dist;
p[e.to] = G[u][i];
Q.push((HeapNode) {
d[e.to], e.to
});
}
}
}
}
};
题目相关
Dijkstra solver;
int n, m, L;
vector<int> gr[maxn][maxn]; // 两点之间的原始边权
int used[maxn][maxn][maxn]; // used[src][a][b]表示源点为src的最短路树是否包含边a->b
int idx[maxn][maxn]; // idx[u][v]为边u->v在Dijkstra求解器中的编号
int sum_single[maxn]; // sum_single[src]表示源点为src的最短路树的所有d之和
int compute_c() {
int ans = 0;
memset(used, 0, sizeof(used));
for(int src = 0; src < n; src++) {
solver.dijkstra(src);
sum_single[src] = 0;
for(int i = 0; i < n; i++) {
if(i != src) {
int fa = solver.edges[solver.p[i]].from;
used[src][fa][i] = used[src][i][fa] = 1;
}
sum_single[src] += (solver.d[i] == INF ? L : solver.d[i]);
}
ans += sum_single[src];
}
return ans;
}
int compute_newc(int a, int b) {
int ans = 0;
for(int src = 0; src < n; src++)
if(!used[src][a][b]) ans += sum_single[src];
else {
solver.dijkstra(src);
for(int i = 0; i < n; i++)
ans += (solver.d[i] == INF ? L : solver.d[i]);
}
return ans;
}
int main() {
while(scanf("%d%d%d", &n, &m, &L) == 3) {
solver.init(n);
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++) gr[i][j].clear();
for(int i = 0; i < m; i++) {
int a, b, s;
scanf("%d%d%d", &a, &b, &s);
a--;
b--;
gr[a][b].push_back(s);
gr[b][a].push_back(s);
}
// 构造网络
for(int i = 0; i < n; i++)
for(int j = i+1; j < n; j++) if(!gr[i][j].empty()) {
sort(gr[i][j].begin(), gr[i][j].end());
solver.AddEdge(i, j, gr[i][j][0]);
idx[i][j] = solver.m - 1;
solver.AddEdge(j, i, gr[i][j][0]);
idx[j][i] = solver.m - 1;
}
int c = compute_c();
int c2 = -1;
for(int i = 0; i < n; i++)
for(int j = i+1; j < n; j++) if(!gr[i][j].empty()) {
int& e1 = solver.edges[idx[i][j]].dist;
int& e2 = solver.edges[idx[j][i]].dist;
if(gr[i][j].size() == 1) e1 = e2 = -1;
else e1 = e2 = gr[i][j][1]; // 大二短边
c2 = max(c2, compute_newc(i, j));
e1 = e2 = gr[i][j][0]; // 恢复
}
printf("%d %d\n", c, c2);
}
return 0;
}
题目:UVa 10537
题意:
有两种节点,一种是大写字母,一种是小写字母。首先输入m条边,当经过小写字母时需要付一单位的过路费,当经过大写字母时,要付当前财务的1/20做过路费。问在起点最少需要带多少物品使到达终点时还有k个物品。当有多条符合条件的路径时输出字典序最小的一个。
分析:
从终点逆着进行更新,用 d[ i ] 表示已经进入了 i 再从 i 到达终点需要准备的最小物资数。那么 d[ e ] 很明显就是输入的那个数。然后就是更新了,dij 和 spfa 都可以,就把所有的 d 都算出来了,然后从起点根据 d 递归打印路劲就行了。两个地方需要注意:(1)由于是d 是逆着算的,有一个地方,我纠结了很久,就是城镇那里,你知道了进入城镇后剩余的量,那怎么求进入之前的量,想了很久,公式退步出来,后来看了人家博客才发现,原来很简单,不用推公式,想想如果 x/20 不是向上取整, x - x/20 = y ,那么 x = 20*y/19,那么现在是向上取整,那么这个 x 肯定是 >= 20*y/19 。然后就是 ++ 暴力往上算凑即可,最多次数是多少,我不知道怎么算(回去后有时间再算算。。 = =),我试验了一下,这个次数很少的,对复杂度没有影响。(2)输出路径的时候,根据 d 进行输出,是递归满足 d[ u ] - w = d[ v ] 的最小的 v ,所以只要从小到大枚举 v 即可,这个w 就很好算了,这是正序的算。
代码:
// UVa10537 Toll! Revisited
// Rujia Liu
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 52 + 10;
const long long INF = 1LL << 60;
typedef long long LL;
int n, G[maxn][maxn], src, dest, p;
int mark[maxn]; // 标记
LL d[maxn]; // d[i]表示从点i出发(已经交过点i的税了)时至少要带多少东西,到dest时还能剩p个东西
int read_node() {
char ch[9];
scanf("%s", ch);
if(ch[0] >= 'A' && ch[0] <= 'Z') return ch[0] - 'A';
else return ch[0] - 'a' + 26;
}
char format_node(int u) {
return u < 26 ? 'A' + u : 'a' + (u - 26);
}
// 拿着item个东西去结点next,还剩多少个东西
LL forward(LL item, int next) {
if(next < 26) return item - (item + 19) / 20;
return item - 1;
}
// 至少要拿着多少个东西到达结点u,交税以后还能剩d[u]个东西
// 为了使代码容易理解,这里使用一种不太数学的解法
LL back(int u) {
if(u >= 26) return d[u]+1;
LL X = d[u] * 20 / 19; // 初始值
while(forward(X, u) < d[u]) X++; // 调整
return X;
}
void solve() {
n = 52; // 总是有52个结点
memset(mark, 0, sizeof(mark));
d[dest] = p;
mark[dest] = 1;
for(int i = 0; i < n; i++) if(i != dest) {
d[i] = INF;
if(G[i][dest]) d[i] = back(dest);
}
// Dijkstra主过程,逆推
while(!mark[src]) {
// 找最小的d
int minu = -1;
for(int i = 0; i < n; i++) if(!mark[i]) {
if(minu < 0 || d[i] < d[minu]) minu = i;
}
mark[minu] = 1;
// 更新其他结点的d
for(int i = 0; i < n; i++) if(!mark[i]) {
if(G[i][minu]) d[i] = min(d[i], back(minu));
}
}
// 输出
printf("%lld\n", d[src]);
printf("%c", format_node(src));
int u = src;
LL item = d[src];
while(u != dest) {
int next;
for(next = 0; next < n; next++) // 找到第一个可以走的结点
if(G[u][next] && forward(item, next) >= d[next]) break;
item = d[next];
printf("-%c", format_node(next));
u = next;
}
printf("\n");
}
int main() {
int kase = 0;
while(scanf("%d", &n) == 1 && n >= 0) {
memset(G, 0, sizeof(G));
for(int i = 0; i < n; i++) {
int u = read_node();
int v = read_node();
if(u != v) G[u][v] = G[v][u] = 1;
}
scanf("%d", &p);
src = read_node();
dest = read_node();
printf("Case %d:\n", ++kase);
solve();
}
return 0;
}