一、最短路
1. poj 2387 Til the Cows Come Home
题意:求从n号结点到1号结点的最短路
思路:spfa最短路
注意:由于此题中路径是双向的,因此声明数组时,和边有关的数组大小应为边数数据范围的两倍。该题因为数组开小了导致一直runtime error。下给出两种实现spfa中队列的方式,一个用stl中的queue,一个直接手写队列。
//使用stl中的queue实现队列
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#include <cstdlib>
#include <algorithm>
using namespace std;
int to[4005], cost[4005], head[1005], nnext[4005], dis[1005];
int vis[1005];
int pt = 0;
void add(int a, int b, int s)
{
to[++ pt] = b;
cost[pt] = s;
nnext[pt] = head[a];
head[a] = pt;
}
void spfa(int n)
{
queue <int> q;
for(int i = 1; i <= n; ++ i)
dis[i] = 0x3f3f3f3f;
dis[n] = 0; vis[n] = 1;
q.push(n);
while(q.empty() == 0)
{
int p = q.front();
q.pop();
vis[p] = 0;
for(int i = head[p]; i != 0; i = nnext[i])
{
if(dis[p] + cost[i] < dis[to[i]])
{
dis[to[i]] = dis[p] + cost[i];
if(vis[to[i]] == 0)
{
vis[to[i]] = 1;
q.push(to[i]);
}
}
}
}
}
int main(void)
{
int t, n;
int a, b, s;
scanf("%d%d", &t, &n);
for(int i = 1; i <= t; ++ i)
{
scanf("%d%d%d", &a, &b, &s);
add(a, b, s);
add(b, a, s);
}
spfa(n);
printf("%d", dis[1]);
return 0;
}
//手写队列实现
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#include <cstdlib>
#include <algorithm>
using namespace std;
int to[4005], cost[4005], he[1005], nnext[4005], dis[1005];
int vis[1005], q[3000000];
int pt = 0;
void add(int a, int b, int s)
{
to[++ pt] = b;
cost[pt] = s;
nnext[pt] = he[a];
he[a] = pt;
}
void spfa(int n)
{
for(int i = 1; i <= n; ++ i)
dis[i] = 0x3f3f3f3f;
dis[n] = 0; vis[n] = 1;
q[1] = n;
for(int l = 0, r = 1; l < r; )
{
int p = q[++ l];
vis[p] = 0;
for(int i = he[p]; i != 0; i = nnext[i])
{
if(dis[p] + cost[i] < dis[to[i]])
{
dis[to[i]] = dis[p] + cost[i];
if(vis[to[i]] == 0)
{
vis[to[i]] = 1;
q[++ r] = to[i];
}
}
}
}
}
int main(void)
{
int t, n;
int a, b, s;
scanf("%d%d", &t, &n);
for(int i = 1; i <= t; ++ i)
{
scanf("%d%d%d", &a, &b, &s);
add(a, b, s);
add(b, a, s);
}
spfa(n);
printf("%d", dis[1]);
return 0;
}
2. hdu 2112 HDU Today
题意:求给定起点终点之间的最短路
思路:对输入地名进行处理,spfa最短路即可
注意:此题坑较多。(1)起点和终点可能相同,此时结果应为0 (2)有可能输入0条路,如果起终点相同则为0,否则为1 (3)该题中的车的路线是无向图,因此还是注意数组开的大小
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#include <cstdlib>
#include <algorithm>
using namespace std;
char d[155][35];
int to[20005], cost[20005], he[155], nnext[20005], dis[155];
int vis[155], q[3000000];
int nd = 0, pt = 0;
void add(int a, int b, int s)
{
to[++ pt] = b;
cost[pt] = s;
nnext[pt] = he[a];
he[a] = pt;
}
void spfa(int n)
{
for(int i = 1; i <= n; ++ i)
dis[i] = 0x3f3f3f3f;
dis[1] = 0; vis[1] = 1;
q[1] = 1;
for(int l = 0, r = 1; l < r; )
{
int p = q[++ l];
vis[p] = 0;
for(int i = he[p]; i != 0; i = nnext[i])
{
if(dis[p] + cost[i] < dis[to[i]])
{
dis[to[i]] = dis[p] + cost[i];
if(vis[to[i]] == 0)
{
vis[to[i]] = 1;
q[++ r] = to[i];
}
}
}
}
}
int main(void)
{
int n, n1, n2, s;
bool ok1, ok2;
char d1[35], d2[35];
while(scanf("%d", &n) != EOF && n != -1)
{
pt = 0; nd = 0;
int flag = 0;
memset(vis, 0, sizeof(vis));
memset(he, 0, sizeof(he));
memset(nnext, 0, sizeof(nnext));
scanf("%s%s", d1, d2);
strcpy(d[1], d1);
strcpy(d[2], d2);
if(strcmp(d1, d2) == 0)
flag = 1;
nd = 2;
for(int i = 1; i <= n; ++ i)
{
scanf("%s %s ", d1, d2);
scanf("%d", &s);
if(flag == 1)
continue;
ok1 = ok2 = false;
for(int j = 1; j <= nd; ++ j)
{
if(strcmp(d[j], d1) == 0)
{
n1 = j;
ok1 = true;
}
if(strcmp(d[j], d2) == 0)
{
n2 = j;
ok2 = true;
}
}
if(ok1 == false)
{
nd ++;
strcpy(d[nd], d1);
n1 = nd;
}
if(ok2 == false)
{
nd ++;
strcpy(d[nd], d2);
n2 = nd;
}
add(n1, n2, s);
add(n2, n1, s);
}
if(flag == 1)
{
printf("0\n");
continue;
}
else if(n == 0)
{
printf("-1\n");
continue;
}
spfa(nd);
if(dis[2] == 0x3f3f3f3f)
printf("-1\n");
else
printf("%d\n", dis[2]);
}
return 0;
}
3.hdu 1596 find the saftest road
题意:每条路有自己的安全系数,在0-1之间,包括0和1,整个路径的安全系数是途径每条路安全系数的乘积。求给定起点终点之间安全系数最大的路径的安全系数。
思路:类似最短路问题。将spfa中间松弛的那步改为 if(dis[i] * cost[i][j] > dis[j]) dis[j] = dis[i] * cost[i][j]即可。求解时,由于求多对路径的最大安全系数。对于每一对,如果从该起点出发的最大系数没有算过,则调用spfa计算从该起点出发到所有结点的最大系数,如果计算过,则直接取计算的值就可以。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#include <cstdlib>
#include <algorithm>
using namespace std;
double cost[1005][1005], sf[1005][1005];
int vis[1005], cal[1005];
int q[3000000];
void spfa(int st, int n)
{
memset(vis, 0, sizeof(vis));
memset(sf[st], 0, sizeof(sf[st]));
int en = (st + 1) % n + 1;
sf[st][st] = 1; vis[st] = 1;
q[1] = st;
for(int l = 0, r = 1; l < r; )
{
int p = q[++ l];
vis[p] = 0;
for(int i = 1; i <= n; ++ i)
{
if(i == p) continue;
if(sf[st][p] * cost[p][i] > sf[st][i])
{
sf[st][i] = sf[st][p] * cost[p][i];
if(vis[i] == 0)
{
vis[i] = 1;
q[++ r] = i;
}
}
}
}
}
int main(void)
{
int n, q, st, en;
while(scanf("%d", &n) != EOF)
{
for(int i = 1; i <= n; ++ i)
for(int j = 1; j <= n; ++ j)
scanf("%lf", &cost[i][j]);
scanf("%d", &q);
for(int i = 1; i <= q; ++ i)
{
scanf("%d%d", &st, &en);
if(en == st)
{
printf("1.000\n");
continue;
}
if(cal[st] == 0)
{
spfa(st, n);
cal[st] = 1;
}
if(sf[st][en] > 0)
printf("%.3f\n", sf[st][en]);
else
printf("What a pity!\n");
}
}
return 0;
}
4. hdu 2680 choose the best route
题意:给定终点以及可能的若干个起点,求所有允许起点到终点的最短路中的最小值。
思路:如果以每个可能的起点算spfa会超时,因此反向建图,以终点为spfa算法的起点,然后比较起到允许起点的时间即可。
注意:这题有一个问题,使用邻接链表存储边用spfa算法超时,考虑可能是重边的影响,邻接表不好处理重边。由于顶点数量在1000以内,因此可以使用邻接矩阵存储边,此时在读入cost值时直接对重边进行处理,再使用spfa算法不超时。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#include <cstdlib>
#include <algorithm>
using namespace std;
int dis[1005], vis[1005], mp[1005][1005];
int q[3000000], pt = 0;
void spfa(int st, int n)
{
for(int i = 1; i <= n; ++ i)
dis[i] = 0x3f3f3f3f;
dis[st] = 0; vis[st] = 1;
q[1] = st;
for(int l = 0, r = 1; l < r; )
{
int p = q[++ l];
vis[p] = 0;
for(int i = 1; i <= n; ++ i)
{
if(dis[p] + mp[p][i] < dis[i])
{
dis[i] = dis[p] + mp[p][i];
if(vis[i] == 0)
{
vis[i] = 1;
q[++ r] = i;
}
}
}
}
}
void init(int n)
{
for(int i = 1; i <= n; ++ i)
{
for(int j = 1; j <= n; ++ j)
{
if(i == j) mp[i][j] = 0;
else mp[i][j] = 0x3f3f3f3f;
}
}
}
int main(void)
{
int n, m, s, w;
int a, b, t, tp;
while(scanf("%d%d%d", &n, &m, &s) != EOF)
{
memset(vis, 0, sizeof(vis));
memset(head, 0, sizeof(head));
memset(nxt, 0, sizeof(nxt));
init(n);
for(int i = 1; i <= m; ++ i)
{
scanf("%d%d%d", &a, &b, &t);
if(t < mp[b][a])
mp[b][a] = t;
}
spfa(s, n);
scanf("%d", &w);
int res = 0x3f3f3f3f;
for(int i = 1; i <= w; ++ i)
{
scanf("%d", &tp);
if(dis[tp] < res)
res = dis[tp];
}
if(res == 0x3f3f3f3f)
printf("-1\n");
else
printf("%d\n", res);
}
return 0;
}
5.hdu 1690 Bus System
题意:在x坐标上有若干车站,两个车站之间的票价由一个分段函数决定,距离越远,票价越高。求给定两车站之的最小值。
思路:此题先根据距离将任意两个车站之间的票价得到,再使用spfa即可。
注意:该题需要注意一下几点,首先结果可能很大,应该用long long存最终结果。由于使用long long 存结果,因此无穷大也必须是使用64为整数的形式,这里用的是0x3f3f3f3f3f3f3f3f。再就是注意输出格式,此题因为输出时少了一个句点wrong answer了好几次。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#include <cstdlib>
#include <algorithm>
using namespace std;
int vis[105], flag[105], pos[105];
int q[3000000], pt = 0;
int l1, l2, l3, l4, c1, c2, c3, c4;
long long dis[105][105], mp[105][105];
void spfa(int st, int n)
{
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= n; ++ i)
dis[st][i] = 0x3f3f3f3f3f3f3f3f;
dis[st][st] = 0; vis[st] = 1;
q[1] = st;
for(int l = 0, r = 1; l < r; )
{
int p = q[++ l];
vis[p] = 0;
for(int i = 1; i <= n; ++ i)
{
if(i == p) continue;
if(dis[st][p] + mp[p][i] < dis[st][i])
{
dis[st][i] = dis[st][p] + mp[p][i];
if(vis[i] == 0)
{
vis[i] = 1;
q[++ r] = i;
}
}
}
}
}
long long get_cost(int x)
{
if(x > 0 && x <= l1) return c1;
if(x > l1 && x <= l2) return c2;
if(x > l2 && x <= l3) return c3;
if(x > l3 && x <= l4) return c4;
if(x > l4) return 0x3f3f3f3f3f3f3f3f;
}
void get_edge(int n)
{
for(int i = 1; i <= n; ++ i) mp[i][i] = 0;
for(int i = 1; i <= n; ++ i)
{
for(int j = i + 1; j <= n; ++ j)
{
mp[i][j] = get_cost(fabs(pos[j] - pos[i]));
mp[j][i] = mp[i][j];
}
}
}
int main(void)
{
int t, n, m, s, e;
scanf("%d", &t);
for(int t0 = 1; t0 <= t; ++ t0)
{
printf("Case %d:\n", t0);
memset(flag, 0, sizeof(flag));
scanf("%d%d%d%d", &l1, &l2, &l3, &l4);
scanf("%d%d%d%d", &c1, &c2, &c3, &c4);
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++ i)
scanf("%d", &pos[i]);
get_edge(n);
for(int i = 1; i <= m; ++ i)
{
scanf("%d%d", &s, &e);
if(flag[s] == 0)
{
spfa(s, n);
flag[s] = 1;
}
if(dis[s][e] == 0x3f3f3f3f3f3f3f3f)
printf("Station %d and station %d are not attainable.\n", s, e);
else
printf("The minimum cost between station %d and station %d is %lld.\n", s, e, dis[s][e]);
}
}
return 0;
}
6.hdu 2433 Travel
题意:给出图中的所有双向路,路长度为1,求去掉已有路中的每一条后,任两个城镇之间的最短路的和是多少。
思路:该题容易想到,针对每条删掉的路,对每一个顶点都重新进行一次最短路的计算然后相加,但直接采用这种方式会超时。当删掉某条路时,某些最短路是不会改变的。首先,计算出完整图中从每个点i出发的最短路树的和sum[i],考虑使用数组used[i][a][b]表示在i的最短路树中是否使用了边ab,如果未使用边ab,那么删掉ab边时就不需要重新计算i的最短路树了。
注意:该题重边的判定也是可以减少计算时间的关键。如果used[i][a][b]是true并且ab间只有一条边的时候,才重新计算i的最短路树,如果ab之间有多条边,那也不需在重新计算了。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#include <cstdlib>
#include <algorithm>
using namespace std;
int q[3000000], pt = 0;
int dis[105], mp[105][105], vis[105], edge[3005][2], sum[105], pre[105], tmp[105][105];
bool used[105][105][105];
void spfa(int st, int n, int choice)
{
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= n; ++ i)
dis[i] = 0x3f3f3f3f;
for(int i = 1; i <= n; ++ i)
pre[i] = i;
dis[st] = 0; vis[st] = 1;
q[1] = st;
for(int l = 0, r = 1; l < r; )
{
int p = q[++ l];
vis[p] = 0;
for(int i = 1; i <= n; ++ i)
{
if(i == p) continue;
int tp;
if(choice == 1)
{
if(mp[p][i] > 1 && mp[p][i] < 0x3f3f3f3f) tp = 1;
else tp = mp[p][i];
}
else if(choice == 2)
{
if(tmp[p][i] > 1 && tmp[p][i] < 0x3f3f3f3f) tp = 1;
else tp = tmp[p][i];
}
if(dis[p] + tp < dis[i])
{
dis[i] = dis[p] + tp;
pre[i] = p;
if(vis[i] == 0)
{
vis[i] = 1;
q[++ r] = i;
}
}
}
}
}
void init(int n)
{
for(int i = 1; i <= n; ++ i)
{
for(int j = 1; j <= n; ++ j)
{
if(i == j) mp[i][j] = 0;
mp[i][j] = 0x3f3f3f3f;
}
}
}
void init1(int n)
{
for(int i = 1; i <= n; ++ i)
{
for(int j = 1; j <= n; ++ j)
{
if(i == j) tmp[i][j] = 0;
tmp[i][j] = 0x3f3f3f3f;
}
}
}
int main(void)
{
int n, m;
while(scanf("%d%d", &n, &m) != EOF)
{
init(n);
for(int i = 1; i <= m; ++ i)
{
scanf("%d%d", &edge[i][0], &edge[i][1]);
int s = edge[i][0], e = edge[i][1];
if(mp[s][e] == 0x3f3f3f3f) mp[s][e] = 1;
else if(mp[s][e] >= 1) mp[s][e] += 1;
else mp[s][e] = 0;
mp[e][s] = mp[s][e];
}
memset(sum, 0, sizeof(sum));
memset(used, 0, sizeof(used));
for(int i = 1; i <= n; ++ i)
{
spfa(i, n, 1);
for(int j = 1; j <= n; ++ j)
{
if(i == j) continue;
sum[i] += dis[j];
used[i][pre[j]][j] = true;
used[i][j][pre[j]] = true;
}
}
for(int i = 1; i <= m; ++ i)
{
int res = 0;
for(int j = 1; j <= n; ++ j)
{
int s = edge[i][0], e = edge[i][1];
if(used[j][s][e] == true && mp[s][e] == 1)
{
init1(n);
for(int i0 = 1; i0 <= m; ++ i0)
{
if(i0 == i) continue;
int s = edge[i0][0], e = edge[i0][1];
if(tmp[s][e] == 0x3f3f3f3f) tmp[s][e] = 1;
else if(tmp[s][e] >= 1) tmp[s][e] += 1;
else tmp[s][e] = 0;
tmp[e][s] = tmp[s][e];
}
spfa(j, n, 2);
for(int k = 1; k <= n; ++ k)
{
if(k == j) continue;
res += dis[k];
if(dis[k] == 0x3f3f3f3f)
{
res = 0x3f3f3f3f;
break;
}
}
}
else
res += sum[j];
if(res == 0x3f3f3f3f)
break;
}
if(res == 0x3f3f3f3f)
printf("INF\n");
else
printf("%d\n", res);
}
}
return 0;
}
二、最小生成树
1.hdu 1863 畅通工程
题意:给定村庄之间的路,求将所有村庄联系起来的最小成本
思路:这是一个最小生成树模板题,直接用kruskal求解即可。
注意:该题最后需要判断是否存在最小生成树。判断的正确方法是在kruskal后记录pre[i] = i的点的数量,如果大于1个,那么不存在最小生成树。最开始用的方法是判断所有点的pre是否都相等,这个是有问题的,如果kruskal算法运行时,5个结点最后一条边合并之前分组是124和35,且35中父节点是3,最后3、4结点合并,合并时并不会改变5的pre,合并结束以后5的pre还是3,但这个时候所有结点都在一个集合中。所以应该通过pre[i]=i的数量来判断。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
struct edge
{
int s, e, cost;
};
int cmp(edge a, edge b)
{
return a.cost < b.cost;
}
edge g[10005];
int pre[105], rnk[105];
void init(int m)
{
for(int i = 1; i <= m; ++ i)
{
pre[i] = i;
rnk[i] = 0;
}
}
int find_rt(int x)
{
if(x == pre[x])
return x;
return pre[x] = find_rt(pre[x]);
}
int union_rnk(int x, int y)
{
int px = find_rt(x);
int py = find_rt(y);
if(px == py)
return 0;
if(rnk[px] < rnk[py])
pre[px] = py;
else
{
pre[py] = px;
if(rnk[px] == rnk[py])
rnk[px] ++;
}
return 1;
}
int kruskal(int n, int m)
{
int res = 0;
sort(g, g + n, cmp);
init(m);
for(int i = 0; i < n; ++ i)
{
int tmp = union_rnk(g[i].s, g[i].e);
res += tmp * g[i].cost;
}
return res;
}
int main(void)
{
int n, m;
while(scanf("%d%d", &n, &m) != EOF && n != 0)
{
for(int i = 0; i < n; ++ i)
scanf("%d%d%d", &g[i].s, &g[i].e, &g[i].cost);
int res = kruskal(n, m);
int cnt = 0;
for(int i = 1; i <= m; ++ i)
{
if(pre[i] == i)
cnt ++;
}
if(cnt > 1)
printf("?\n");
else
printf("%d\n", res);
}
return 0;
}
该题也可使用prim算法,在prim中用res记录最小值,在判断中只要发现不连通的两部分,就马上退出算法。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
int cost[105][105], vis[105], dis[105];
int prim(int m)
{
int res = 0;
for(int i = 1; i <= m; ++ i)
dis[i] = cost[1][i];
dis[1] = 0; vis[1] = 1;
for(int i = 2; i <= m; ++ i)
{
int mins = 0x3f3f3f3f, x = 0;
for(int j = 1; j <= m; ++ j)
{
if(vis[j] == 0 && dis[j] < mins)
{
mins = dis[j];
x = j;
}
}
if(x == 0) return 0x3f3f3f3f;
res += dis[x];
vis[x] = 1;
for(int j = 1; j <= m; ++ j)
{
if(vis[j] == 0 && cost[x][j] < dis[j])
dis[j] = cost[x][j];
}
}
return res;
}
int main(void)
{
int n, m, s, e, t;
while(scanf("%d%d", &n, &m) != EOF && n != 0)
{
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= m; ++ i)
{
for(int j = 1; j <= m; ++ j)
{
if(i == j) cost[i][j] = 0;
else cost[i][j] = 0x3f3f3f3f;
}
}
for(int i = 0; i < n; ++ i)
{
scanf("%d%d%d", &s, &e, &t);
cost[s][e] = t;
cost[e][s] = t;
}
int res = prim(m);
if(res == 0x3f3f3f3f)
printf("?\n");
else
printf("%d\n", res);
}
return 0;
}
2.hdu 3371 connect the cities
题意:有某些城市之间有连接,现修路使所有城市均连在一起。
思路:将已连接的城市先用并查集处理一下,然后再用kruskal求最小生成树即可。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
struct edge
{
int s, e, cost;
};
edge g[25005];
int pre[505], rnk[505];
int cmp(edge a, edge b)
{
return a.cost < b.cost;
}
void init(int n)
{
for(int i = 1; i <= n; ++ i)
{
pre[i] = i;
rnk[i] = 0;
}
}
int find_rt(int x)
{
if(pre[x] == x)
return x;
return pre[x] = find_rt(pre[x]);
}
int union_rnk(int x, int y)
{
int px = find_rt(x);
int py = find_rt(y);
if(px == py)
return 0;
if(rnk[px] < rnk[py])
pre[px] = py;
else
{
pre[py] = px;;
if(rnk[px] == rnk[py])
rnk[px] ++;
}
return 1;
}
int kruskal(int n, int m)
{
int res = 0;
sort(g, g + m, cmp);
for(int i = 0; i < m; ++ i)
{
int tmp = union_rnk(g[i].s, g[i].e);
res += tmp * g[i].cost;
}
return res;
}
int main(void)
{
int t, n, m, k, nt, ct1, ct2;
scanf("%d", &t);
for(int t0 = 1; t0 <= t; ++ t0)
{
scanf("%d%d%d", &n, &m, &k);
init(n);
for(int i = 0; i < m; ++ i)
scanf("%d%d%d", &g[i].s, &g[i].e, &g[i].cost);
for(int i = 1; i <= k; ++ i)
{
scanf("%d", &nt);
scanf("%d", &ct1);
for(int j = 2; j <= nt; ++ j)
{
scanf("%d", &ct2);
union_rnk(ct1, ct2);
}
}
int res = kruskal(n, m);
int cnt = 0;
for(int i = 1; i <= n; ++ i)
{
if(pre[i] == i)
cnt ++;
}
if(cnt > 1)
printf("-1\n");
else
printf("%d\n", res);
}
return 0;
}