上节课解决的最短路是在具有某些特点的图上进行的。对于更广泛的图,
求最短路是有专门算法的。
一 迪杰斯特拉(DIJKSTRA)算法
它继承了广搜的“松弛”特性,是一种加权的松弛动归刷新算法。
解决从一个定点出发到另一点或所有点的最短路问题。
细致概念见百度百科,下面直接给出其矩阵存储形式的简化代码描述
#include<iostream>
using namespace std;
int main()
{//求从1到n的最短路
int i,j,n,m,a,b,p,q,r,l;
int f[101],g[101][101],h[101]={};
memset(f,4,sizeof(f));
memset(g,4,sizeof(g));
cin>>n>>m;//n个点m条边
for(i=1;i<=m;i++)
{
cin>>p>>q>>r;
g[p][q]=r;
g[q][p]=r;
}
f[1]=0;
h[1]=1;//表示1号点已经永久标记
l=1;//前面永久标记过的最后一点事1
for(i=1;i<n;i++)
{
p=0;//用来记录临时标记点中f最小一点的点号
for(j=1;j<=n;j++)
if(h[j]<1)//j是临时标记点
{
if(f[l]+g[l][j]<f[j])f[j]=f[l]+g[l][j];
if(f[j]<f[p]) p=j;
}
l=p;
h[p]=1;//该点即被标定
}
for (i = 1; i <= n; i++)
cout << f[i] << ' ';
cout << endl;
system("pause");
return 0;
}
此算法的两个要点是松弛(刷新)和选小,也就是那两个最长的if语句行。
在许多情况下,可以得到比上面N平方算法更简化的描述。
例如刷新范围可能被缩小,每次选小代价可能降为log2(n)甚至o(1)。
二 弗洛伊德算法
它的原理与迪杰斯特拉算法相似,但解决的是多点对多点的最短路问题。
所以计算量大约是后者的n倍。参考代码如下:
#include<iostream>
using namespace std;
int f[101][101];
int main()
{
int x,y,z,n,m;
memset(f,63,sizeof(f));
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>x>>y>>z;
f[x][y]=z;
f[y][x]=z;
}
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(f[i][k]+f[k][j]<f[i][j])
f[i][j]=f[i][k]+f[k][j];
while(cin>>x>>y)//重复输出多对x、y间的距离
cout<<f[x][y]<<endl;
return 0;
}
三贝尔曼福德算法
每轮对所有边或弧进行出点对入点的刷新,n-1轮后所有点与出发点的距离就被求出了。
它也是从一点出发走向所有点求最短路的算法。
下面给出贝尔曼福德算法的简单代码描述:
#include<iostream>
#include<string.h>
using namespace std;
int main()
{//从k号点出发求到所有点的最短路长度
int m,n,i,j,k,p,q,r;
int f[100],a[5000],b[5000],c[5000];
cin>>n>>m>>k;
for(i=1;i<=m;i++)
cin>>a[i]>>b[i]>>c[i];
memset(f,1,sizeof(f));
f[k]=0;
for(i=1;i<n;i++)
for(j=1;j<=m;j++)
{
f[a[j]]=min(f[a[j]],f[b[j]]+c[j]);
f[b[j]]=min(f[b[j]],f[a[j]]+c[j]);
}
for(i=1;i<=n;i++)
cout<<f[i]<<' ';
cout<<endl;
return 0;
}
四 SPFA最短路算法
上面三的算法计算量有可能达到N的三次方,其中许多次刷新显然是不起作用的。
基于这种认识,我们可以只做有可能奏效的那些比较和刷新。
具体地说,任务队列中仅保存刷新成功的点,因为没被刷新成功的点不可能对其他点刷新成功。
下面的代码既使用了邻接矩阵又使用了邻接表,
美中不足是邻接表没有用VECTOR,存储空间定义不尽合理。
#include<iostream>
using namespace std;
int main()
{
int i,n,m,p,q,r;
int f[101], g[101][101], a[10001], t = 1, w = 0, b[101][101] = {};
bool h[101];
memset(f, 4, sizeof(f));
memset(g, 4, sizeof(g));
cin >> n >> m;
for(i = 1; i <= m; i++)
{
cin >> p >> q >> r;
g[p][q] = r;
b[p][++b[p][0]]=q;
b[q][++b[q][0]] = p;
}
f[1] = 0;//从1出发
h[1]=1;//1已经在任务队列中
a[++w] = 1;//1号点进入任务队列
while(t <= w)
{//下面的b是邻接表,b[][0]是表长
for(i=1;i<=b[a[t]][0];i++)
{
if(f[a[t]]+g[a[t]][b[a[t]][i]]<=f[b[a[t]][i]])
{
f[b[a[t]][i]] = f[a[t]] + g[a[t]][b[a[t]][i]];
if (h[b[a[t]][i]] == 0)
{//若未在任务队列中就进队
h[b[a[t]][i]] = 1;
a[++w] = b[a[t]][i];
}
}
}
h[a[t]] = 0;
t++;
}
for (i = 1; i <= n; i++)
cout<<f[i] << ' ';
cout << endl;
system("pause");
return 0;
}
为体现这些算法解决竞赛题的实力,
我们仍以曾记忆化深搜过的“排雷”为例,这显然是一个最短路问题。
例题1 用DJS算法和SPFA算法分别解决排雷问题
1 DJS
#include<fstream>
#include<cstdlib>
#include <algorithm>
using namespace std;
ifstream cin("mine.in");
ofstream cout("mine.out");
int a[40][40],b[40][40],f[40][40];
main()
{
int min,x,y,i,j,n,x0,y0,x1,y1;
cin >> n;
cin >> x0 >> y0;
cin >> x1 >> y1;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
cin >> a[i][j];
f[i][j]=1000000000;
}
f[x0][y0]=a[x0][y0];
f[0][0]=INT_MAX;
for(b[x0][y0]=1;b[x1][y1]==0;b[x][y]=1)
{//先用4个if语句向4个方向松弛、刷新,未用方向矢量
if(a[x0+1][y0]+f[x0][y0]<f[x0+1][y0])
f[x0+1][y0]=a[x0+1][y0]+f[x0][y0];
if(a[x0-1][y0]+f[x0][y0]<f[x0-1][y0])
f[x0-1][y0]=a[x0-1][y0]+f[x0][y0];
if(a[x0][y0+1]+f[x0][y0]<f[x0][y0+1])
f[x0][y0+1]=a[x0][y0+1]+f[x0][y0];
if(a[x0][y0-1]+f[x0][y0]<f[x0][y0-1])
f[x0][y0-1]=a[x0][y0-1]+f[x0][y0];
x=y=0;
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
if(b[i][j]==0 && f[i][j]<f[x][y])
{
x=i;
y=j;
}
}
}
x0=x;
y0=y;
}
cout << f[x1][y1];
return 0;
}
2 SPFA
#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <queue>
using namespace std;
int a[40][40]={},b[40][40]={},f[40][40];
const int x[4]={1,-1,0,0},y[4]={0,0,1,-1};
main()
{
queue< pair<int,int> > q;
int i,j,n,x0,y0,x1,y1,k;
cin >> n;
cin >> x0 >> y0;
cin >> x1 >> y1;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
cin >> a[i][j];
f[i][j]=1000000000;
}
f[x0][y0]=a[x0][y0];
q.push(make_pair(x0,y0));任务队列初始化
while(!q.empty())
{
x0=q.front().first;
y0=q.front().second;
q.pop();
b[x0][y0]=0;
for(k=0;k<4;k++)
{
i=x0+x[k];
j=y0+y[k];
if(a[i][j]+f[x0][y0]<f[i][j])
{
f[i][j]=a[i][j]+f[x0][y0];
if(b[i][j]==0)
{//(i,j)进队
q.push(make_pair(i,j));
b[i][j]=1;
}
}
}
}
cout << f[x1][y1];
system("pause");
return 0;
}
例题2 用弗洛伊德算法搜索每个数字能到达的点数,最终解决“产生数”问题。
#include<iostream>
#include<cstring>
#include<stdio.h>
using namespace std;
int f[16][16],b[16];
void flyd()
{
int i,j,k;
for(k=0;k<=9;k++)
for(i=0;i<=9;i++)
for(j=0;j<=9;j++)
if(f[i][k]+f[k][j]<f[i][j])f[i][j]=f[i][k]+f[k][j];
for(i=0;i<=9;i++)
{
f[i][i]=1;
for(j=0;j<=9;j++)
b[i]+=f[i][j]<10;
}
}
int main()
{
char a[32];
int i,k,x,y;
double t=1;
freopen("p3.in","r",stdin);
freopen("p3.out","w",stdout);
cin>>a>>k;
memset(f,1,sizeof(f));
for(i=1;i<=k;i++)
{
cin>>x>>y;
f[x][y]=1;
}
flyd();
for(i=0;i<strlen(a);i++)
t*=b[a[i]-'0'];
printf("%.00f\n",t);
return 0;
}
此代码中变量t的类型和输出格式耐人寻味,你想通了吗?
例题3 NOIP普及组2012 文化之旅
4.文化之旅
(culture.cpp/c/pas)
【问题描述】
有一位使者要游历各国,他每到一个国家,都能学到一种文化,
但他不愿意学习任何一种文化超过一次(即如果他学习了某种文化,则他就不能到达其他有这种文化的国家)
。不同的国家可能有相同的文化。
不同文化的国家对其他文化的看法不同,有些文化会排斥外来文化(即如果他学习了某种文化,则他不能到达排斥这种文化的其他国家)。
现给定各个国家间的地理关系,各个国家的文化,每种文化对其他文化的看法,
以及这位使者游历的起点和终点(在起点和终点也会学习当地的文化)
,国家间的道路距离,试求从起点到终点最少需走多少路。
【输入】
输入文件 culture.in。
第一行为五个整数 N,K,M,S,T,每两个整数之间用一个空格隔开,
依次代表国家个数(国家编号为1到N)
,文化种数(文化编号为 1 到 K)
,道路的条数,以及起点和终点的编号(保证S不等于T);
第二行为N个整数,每两个整数之间用一个空格隔开,其中第i个数Ci,表示国家i的文化为Ci。
接下来的 K 行,每行 K 个整数,每两个整数之间用一个空格隔开,记第 i 行的第 j 个数
为aij,aij=1表示文化i排斥外来文化j(i等于j时表示排斥相同文化的外来人)aij= 0表示不排斥
,(注意 i排斥j并不保证j一定也排斥i)。
接下来的 M 行,每行三个整数 u,v,d,每两个整数之间用一个空格隔开,表示国家 u
与国家 v 有一条距离为 d 的可双向通行的道路(保证 u 不等于 v,两个国家之间可能有多条
道路)。【输出】
输出文件名为 culture.out。
输出只有一行,一个整数,表示使者从起点国家到达终点国家最少需要走的距离数(如
果无解则输出-1)。
【输入输出样例 1】
culture.in
2
1
0
1
1
2 1 1 2
2
1
0
2 10
culture.out
-1
【输入输出样例说明】
由于到国家 2 必须要经过国家 1,而国家 2 的文明却排斥国家 1 的文明,所以不可能到
达国家 2。
【输入输出样例 2】
culture.in
2 2 1 1 2
1 2
0 1
0 0
1 2 10
culture.out
10
【输入输出样例说明】
路线为 1 -> 2。
【数据范围】
对于 20%的数据,有 2≤N≤8,K≤5;
对于 30%的数据,有 2≤N≤10,K≤5;
对于 50%的数据,有 2≤N≤20,K≤8;
对于 70%的数据,有 2≤N≤100,K≤10;
对于 100%的数据,有 2<N<100,1<K<100,1<M<N 2,1≤ki≤K,1≤u, v≤N,1≤d≤1000,
S≠T,1 ≤S, T≤N。
分析 这显然是一个加了限制的最短路问题
1 用贝尔曼福德算法加文化矛盾的判断
#include<fstream>
#include<algorithm>
using namespace std;
ifstream cin("culture.in");
ofstream cout("culture.out");
int a[3001],b[3001],c[3001],d[101][101],e[1001],f[101];
int main()
{
int i,j,n,k,m,s,t,x,y;
cin>>n>>k>>m>>s>>t;
memset(f,63,sizeof(f));
for(i=1;i<=n;i++)
cin>>e[i];
for(i=1;i<=k;i++)
{
for(j=1;j<=k;j++)
cin>>d[i][j];
d[i][i]=1;
}
for(i=0;i<m;i++)
cin>>a[i]>>b[i]>>c[i];
f[s]=0;
for(i=1;i<n;i++)
for(j=0;j<m;j++)
{
x=a[j];
y=b[j];
if(d[e[y]][e[x]]==0 && f[x]+c[j]<f[y])
f[y]=f[x]+c[j];
if(d[e[x]][e[y]]==0 && f[y]+c[j]<f[x])
f[x]=f[y]+c[j];
}
if(f[t]>1000000000)
cout<<-1<<endl;
else
cout<<f[t]<<endl;
return 0;
}
2 逆向前效记忆搜索
#include<fstream>
#include<math.h>
#include<stdio.h>
#include<stack>
#include<string.h>
#include<bitset>
#include<algorithm>
using namespace std;
ifstream cin("culture.in");
ofstream cout("culture.out");
struct node
{
int x;
bitset <101> d;
};
stack<node> sk;
bitset<101> a[101],d;
int g[101][101];
int f[101];
int main()
{
int i, j, u, v, z, n, k, m ,s ,t, c[101];
node p;
memset(f,63,sizeof(f));
cin >> n >> k >> m >> s >> t;
for (i = 1; i <= n; i++)
cin >> c[i];
for (i = 1; i <= k; i++)
{
for (j = 1; j <= k; j++)
{
cin >> u;
a[i][j] = u;
}
a[i][i]=1;
}
for (i = 1; i <= m; i++)
{
cin >> u >> v >> z;
g[u][v] = g[v][u] = z;
}
p.x=t;
p.d=a[c[t]];
sk.push(p);
d=a[c[t]];//d表示抵制文化的集合
f[t]=0;
int q=0;
while(!sk.empty())
{
if(++q>n)
{
if(q==s)
break;
q=sk.top().x;
sk.pop();
if(!sk.empty())
d=sk.top().d;
}
else if(g[sk.top().x][q]>0 && d[c[q]]==0 && f[sk.top().x]+g[sk.top().x][q]<f[q])
{
f[q]=f[sk.top().x]+g[sk.top().x][q];//对q点的f进行记忆
p.x=q;
p.d=d|=a[c[q]];
sk.push(p);
q=0;
}
}
if(f[s]<1000000000)
cout<<f[s]<<endl;
else
cout<<"-1"<<endl;
return 0;
}
练习
1 奇怪的电梯
在N层楼中有一部升降式电梯,电梯间的门上有一个玻璃窗,透过它可以看到电梯所在楼层墙上写着的一个正整数。表示在此层楼按“向上”键或“向下”键能够生或降的层数变化量。电梯中除了这两个键没有别的键可按。
求从A层到B层至少移动过多少层?
1<n<100
2 NOIP提高组2001 CAR的旅行路线
program project1;
type
rec = record
x, y, c: longint;
end;
var
n, tot, costf, st, ed, tc: longint;
pos: array[0..2000] of rec;
x, y: array[1..4] of longint;
costt: array[0..200] of longint;
b: array[0..200] of boolean;
d: array[0..100] of real;
procedure init;
var
i, j, k: longint;
begin
readln(n,costf,st,ed);
for i := 1 to n do
begin
readln(x[1],y[1],x[2],y[2],x[3],y[3],costt[i]);
for j := 1 to 3 do
for k := 1 to 3 do
if j <> k then
if (x[j]-x[k])*(x[6-k-j]-x[j])+(y[j]-y[k])*(y[6-k-j]-y[j]) = 0 then
begin
x[4] := x[k]+x[6-k-j]-x[j];
y[4] := y[k]+y[6-k-j]-y[j];
end;
for j := 1 to 4 do
begin
inc(tot);
pos[tot].x := x[j];
pos[tot].y := y[j];
pos[tot].c := i;
end;
end;
//for i := 1 to tot do writeln(pos[i].x,' ',pos[i].y,' ',pos[i].c);
end;
function dis(x, y: longint):real;
begin
dis := sqrt(sqr(pos[x].x-pos[y].x)+sqr(pos[x].y-pos[y].y));
if pos[x].c = pos[y].c then
dis := dis*costt[pos[x].c]
else dis := dis*costf;
end;
function dij(st: longint):real;
var
i, j, p: longint;
min: real;
begin
fillchar(b,sizeof(b),false);
fillchar(d,sizeof(d),100);
d[st] := 0;
for i := 1 to tot do
begin
min := 1e38;
for j := 1 to tot do
if (not b[j]) and (d[j] < min) then
begin
min := d[j];
p := j;
end;
b[p] := true;
for j := 1 to tot do
if not b[j] then
if d[j] > d[p]+dis(p,j) then d[j] := d[p]+dis(p,j);
end;
dij := 1e38;
for i := 1 to tot do
begin
if pos[i].c = ed then
for j := 0 to 3 do
if d[i+j] < dij then dij := d[i+j];
if pos[i].c = ed then break;
end;
end;
procedure work;
var
i, j: longint;
min, temp: real;
begin
min := 1e38;
for i := 1 to tot do
begin
if pos[i].c = st then
for j := 0 to 3 do
begin
temp := dij(i+j);
if temp < min then min := temp;
end;
if pos[i].c = st then break;
end;
writeln(min:0:1);
end;
begin
assign(input,'d:\in.txt');reset(input);
assign(output,'d:\out.txt');rewrite(output);
readln(tc);
while tc > 0 do
begin
init;
work;
dec(tc);
end;
close(input);
close(output);
end.
3 CCC2009 pencil 买铅笔
4 NOIP提高组2009 最优贸易
5 把海淀竞赛“排雷”用DJS的堆优化方式重新描述
选作NOIP提高组2013DAY2华容道
求最短路是有专门算法的。
一 迪杰斯特拉(DIJKSTRA)算法
它继承了广搜的“松弛”特性,是一种加权的松弛动归刷新算法。
解决从一个定点出发到另一点或所有点的最短路问题。
细致概念见百度百科,下面直接给出其矩阵存储形式的简化代码描述
#include<iostream>
using namespace std;
int main()
{//求从1到n的最短路
int i,j,n,m,a,b,p,q,r,l;
int f[101],g[101][101],h[101]={};
memset(f,4,sizeof(f));
memset(g,4,sizeof(g));
cin>>n>>m;//n个点m条边
for(i=1;i<=m;i++)
{
cin>>p>>q>>r;
g[p][q]=r;
g[q][p]=r;
}
f[1]=0;
h[1]=1;//表示1号点已经永久标记
l=1;//前面永久标记过的最后一点事1
for(i=1;i<n;i++)
{
p=0;//用来记录临时标记点中f最小一点的点号
for(j=1;j<=n;j++)
if(h[j]<1)//j是临时标记点
{
if(f[l]+g[l][j]<f[j])f[j]=f[l]+g[l][j];
if(f[j]<f[p]) p=j;
}
l=p;
h[p]=1;//该点即被标定
}
for (i = 1; i <= n; i++)
cout << f[i] << ' ';
cout << endl;
system("pause");
return 0;
}
此算法的两个要点是松弛(刷新)和选小,也就是那两个最长的if语句行。
在许多情况下,可以得到比上面N平方算法更简化的描述。
例如刷新范围可能被缩小,每次选小代价可能降为log2(n)甚至o(1)。
二 弗洛伊德算法
它的原理与迪杰斯特拉算法相似,但解决的是多点对多点的最短路问题。
所以计算量大约是后者的n倍。参考代码如下:
#include<iostream>
using namespace std;
int f[101][101];
int main()
{
int x,y,z,n,m;
memset(f,63,sizeof(f));
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>x>>y>>z;
f[x][y]=z;
f[y][x]=z;
}
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(f[i][k]+f[k][j]<f[i][j])
f[i][j]=f[i][k]+f[k][j];
while(cin>>x>>y)//重复输出多对x、y间的距离
cout<<f[x][y]<<endl;
return 0;
}
三贝尔曼福德算法
每轮对所有边或弧进行出点对入点的刷新,n-1轮后所有点与出发点的距离就被求出了。
它也是从一点出发走向所有点求最短路的算法。
下面给出贝尔曼福德算法的简单代码描述:
#include<iostream>
#include<string.h>
using namespace std;
int main()
{//从k号点出发求到所有点的最短路长度
int m,n,i,j,k,p,q,r;
int f[100],a[5000],b[5000],c[5000];
cin>>n>>m>>k;
for(i=1;i<=m;i++)
cin>>a[i]>>b[i]>>c[i];
memset(f,1,sizeof(f));
f[k]=0;
for(i=1;i<n;i++)
for(j=1;j<=m;j++)
{
f[a[j]]=min(f[a[j]],f[b[j]]+c[j]);
f[b[j]]=min(f[b[j]],f[a[j]]+c[j]);
}
for(i=1;i<=n;i++)
cout<<f[i]<<' ';
cout<<endl;
return 0;
}
四 SPFA最短路算法
上面三的算法计算量有可能达到N的三次方,其中许多次刷新显然是不起作用的。
基于这种认识,我们可以只做有可能奏效的那些比较和刷新。
具体地说,任务队列中仅保存刷新成功的点,因为没被刷新成功的点不可能对其他点刷新成功。
下面的代码既使用了邻接矩阵又使用了邻接表,
美中不足是邻接表没有用VECTOR,存储空间定义不尽合理。
#include<iostream>
using namespace std;
int main()
{
int i,n,m,p,q,r;
int f[101], g[101][101], a[10001], t = 1, w = 0, b[101][101] = {};
bool h[101];
memset(f, 4, sizeof(f));
memset(g, 4, sizeof(g));
cin >> n >> m;
for(i = 1; i <= m; i++)
{
cin >> p >> q >> r;
g[p][q] = r;
b[p][++b[p][0]]=q;
b[q][++b[q][0]] = p;
}
f[1] = 0;//从1出发
h[1]=1;//1已经在任务队列中
a[++w] = 1;//1号点进入任务队列
while(t <= w)
{//下面的b是邻接表,b[][0]是表长
for(i=1;i<=b[a[t]][0];i++)
{
if(f[a[t]]+g[a[t]][b[a[t]][i]]<=f[b[a[t]][i]])
{
f[b[a[t]][i]] = f[a[t]] + g[a[t]][b[a[t]][i]];
if (h[b[a[t]][i]] == 0)
{//若未在任务队列中就进队
h[b[a[t]][i]] = 1;
a[++w] = b[a[t]][i];
}
}
}
h[a[t]] = 0;
t++;
}
for (i = 1; i <= n; i++)
cout<<f[i] << ' ';
cout << endl;
system("pause");
return 0;
}
为体现这些算法解决竞赛题的实力,
我们仍以曾记忆化深搜过的“排雷”为例,这显然是一个最短路问题。
例题1 用DJS算法和SPFA算法分别解决排雷问题
1 DJS
#include<fstream>
#include<cstdlib>
#include <algorithm>
using namespace std;
ifstream cin("mine.in");
ofstream cout("mine.out");
int a[40][40],b[40][40],f[40][40];
main()
{
int min,x,y,i,j,n,x0,y0,x1,y1;
cin >> n;
cin >> x0 >> y0;
cin >> x1 >> y1;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
cin >> a[i][j];
f[i][j]=1000000000;
}
f[x0][y0]=a[x0][y0];
f[0][0]=INT_MAX;
for(b[x0][y0]=1;b[x1][y1]==0;b[x][y]=1)
{//先用4个if语句向4个方向松弛、刷新,未用方向矢量
if(a[x0+1][y0]+f[x0][y0]<f[x0+1][y0])
f[x0+1][y0]=a[x0+1][y0]+f[x0][y0];
if(a[x0-1][y0]+f[x0][y0]<f[x0-1][y0])
f[x0-1][y0]=a[x0-1][y0]+f[x0][y0];
if(a[x0][y0+1]+f[x0][y0]<f[x0][y0+1])
f[x0][y0+1]=a[x0][y0+1]+f[x0][y0];
if(a[x0][y0-1]+f[x0][y0]<f[x0][y0-1])
f[x0][y0-1]=a[x0][y0-1]+f[x0][y0];
x=y=0;
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
if(b[i][j]==0 && f[i][j]<f[x][y])
{
x=i;
y=j;
}
}
}
x0=x;
y0=y;
}
cout << f[x1][y1];
return 0;
}
2 SPFA
#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <queue>
using namespace std;
int a[40][40]={},b[40][40]={},f[40][40];
const int x[4]={1,-1,0,0},y[4]={0,0,1,-1};
main()
{
queue< pair<int,int> > q;
int i,j,n,x0,y0,x1,y1,k;
cin >> n;
cin >> x0 >> y0;
cin >> x1 >> y1;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
cin >> a[i][j];
f[i][j]=1000000000;
}
f[x0][y0]=a[x0][y0];
q.push(make_pair(x0,y0));任务队列初始化
while(!q.empty())
{
x0=q.front().first;
y0=q.front().second;
q.pop();
b[x0][y0]=0;
for(k=0;k<4;k++)
{
i=x0+x[k];
j=y0+y[k];
if(a[i][j]+f[x0][y0]<f[i][j])
{
f[i][j]=a[i][j]+f[x0][y0];
if(b[i][j]==0)
{//(i,j)进队
q.push(make_pair(i,j));
b[i][j]=1;
}
}
}
}
cout << f[x1][y1];
system("pause");
return 0;
}
例题2 用弗洛伊德算法搜索每个数字能到达的点数,最终解决“产生数”问题。
#include<iostream>
#include<cstring>
#include<stdio.h>
using namespace std;
int f[16][16],b[16];
void flyd()
{
int i,j,k;
for(k=0;k<=9;k++)
for(i=0;i<=9;i++)
for(j=0;j<=9;j++)
if(f[i][k]+f[k][j]<f[i][j])f[i][j]=f[i][k]+f[k][j];
for(i=0;i<=9;i++)
{
f[i][i]=1;
for(j=0;j<=9;j++)
b[i]+=f[i][j]<10;
}
}
int main()
{
char a[32];
int i,k,x,y;
double t=1;
freopen("p3.in","r",stdin);
freopen("p3.out","w",stdout);
cin>>a>>k;
memset(f,1,sizeof(f));
for(i=1;i<=k;i++)
{
cin>>x>>y;
f[x][y]=1;
}
flyd();
for(i=0;i<strlen(a);i++)
t*=b[a[i]-'0'];
printf("%.00f\n",t);
return 0;
}
此代码中变量t的类型和输出格式耐人寻味,你想通了吗?
例题3 NOIP普及组2012 文化之旅
4.文化之旅
(culture.cpp/c/pas)
【问题描述】
有一位使者要游历各国,他每到一个国家,都能学到一种文化,
但他不愿意学习任何一种文化超过一次(即如果他学习了某种文化,则他就不能到达其他有这种文化的国家)
。不同的国家可能有相同的文化。
不同文化的国家对其他文化的看法不同,有些文化会排斥外来文化(即如果他学习了某种文化,则他不能到达排斥这种文化的其他国家)。
现给定各个国家间的地理关系,各个国家的文化,每种文化对其他文化的看法,
以及这位使者游历的起点和终点(在起点和终点也会学习当地的文化)
,国家间的道路距离,试求从起点到终点最少需走多少路。
【输入】
输入文件 culture.in。
第一行为五个整数 N,K,M,S,T,每两个整数之间用一个空格隔开,
依次代表国家个数(国家编号为1到N)
,文化种数(文化编号为 1 到 K)
,道路的条数,以及起点和终点的编号(保证S不等于T);
第二行为N个整数,每两个整数之间用一个空格隔开,其中第i个数Ci,表示国家i的文化为Ci。
接下来的 K 行,每行 K 个整数,每两个整数之间用一个空格隔开,记第 i 行的第 j 个数
为aij,aij=1表示文化i排斥外来文化j(i等于j时表示排斥相同文化的外来人)aij= 0表示不排斥
,(注意 i排斥j并不保证j一定也排斥i)。
接下来的 M 行,每行三个整数 u,v,d,每两个整数之间用一个空格隔开,表示国家 u
与国家 v 有一条距离为 d 的可双向通行的道路(保证 u 不等于 v,两个国家之间可能有多条
道路)。【输出】
输出文件名为 culture.out。
输出只有一行,一个整数,表示使者从起点国家到达终点国家最少需要走的距离数(如
果无解则输出-1)。
【输入输出样例 1】
culture.in
2
1
0
1
1
2 1 1 2
2
1
0
2 10
culture.out
-1
【输入输出样例说明】
由于到国家 2 必须要经过国家 1,而国家 2 的文明却排斥国家 1 的文明,所以不可能到
达国家 2。
【输入输出样例 2】
culture.in
2 2 1 1 2
1 2
0 1
0 0
1 2 10
culture.out
10
【输入输出样例说明】
路线为 1 -> 2。
【数据范围】
对于 20%的数据,有 2≤N≤8,K≤5;
对于 30%的数据,有 2≤N≤10,K≤5;
对于 50%的数据,有 2≤N≤20,K≤8;
对于 70%的数据,有 2≤N≤100,K≤10;
对于 100%的数据,有 2<N<100,1<K<100,1<M<N 2,1≤ki≤K,1≤u, v≤N,1≤d≤1000,
S≠T,1 ≤S, T≤N。
分析 这显然是一个加了限制的最短路问题
1 用贝尔曼福德算法加文化矛盾的判断
#include<fstream>
#include<algorithm>
using namespace std;
ifstream cin("culture.in");
ofstream cout("culture.out");
int a[3001],b[3001],c[3001],d[101][101],e[1001],f[101];
int main()
{
int i,j,n,k,m,s,t,x,y;
cin>>n>>k>>m>>s>>t;
memset(f,63,sizeof(f));
for(i=1;i<=n;i++)
cin>>e[i];
for(i=1;i<=k;i++)
{
for(j=1;j<=k;j++)
cin>>d[i][j];
d[i][i]=1;
}
for(i=0;i<m;i++)
cin>>a[i]>>b[i]>>c[i];
f[s]=0;
for(i=1;i<n;i++)
for(j=0;j<m;j++)
{
x=a[j];
y=b[j];
if(d[e[y]][e[x]]==0 && f[x]+c[j]<f[y])
f[y]=f[x]+c[j];
if(d[e[x]][e[y]]==0 && f[y]+c[j]<f[x])
f[x]=f[y]+c[j];
}
if(f[t]>1000000000)
cout<<-1<<endl;
else
cout<<f[t]<<endl;
return 0;
}
2 逆向前效记忆搜索
#include<fstream>
#include<math.h>
#include<stdio.h>
#include<stack>
#include<string.h>
#include<bitset>
#include<algorithm>
using namespace std;
ifstream cin("culture.in");
ofstream cout("culture.out");
struct node
{
int x;
bitset <101> d;
};
stack<node> sk;
bitset<101> a[101],d;
int g[101][101];
int f[101];
int main()
{
int i, j, u, v, z, n, k, m ,s ,t, c[101];
node p;
memset(f,63,sizeof(f));
cin >> n >> k >> m >> s >> t;
for (i = 1; i <= n; i++)
cin >> c[i];
for (i = 1; i <= k; i++)
{
for (j = 1; j <= k; j++)
{
cin >> u;
a[i][j] = u;
}
a[i][i]=1;
}
for (i = 1; i <= m; i++)
{
cin >> u >> v >> z;
g[u][v] = g[v][u] = z;
}
p.x=t;
p.d=a[c[t]];
sk.push(p);
d=a[c[t]];//d表示抵制文化的集合
f[t]=0;
int q=0;
while(!sk.empty())
{
if(++q>n)
{
if(q==s)
break;
q=sk.top().x;
sk.pop();
if(!sk.empty())
d=sk.top().d;
}
else if(g[sk.top().x][q]>0 && d[c[q]]==0 && f[sk.top().x]+g[sk.top().x][q]<f[q])
{
f[q]=f[sk.top().x]+g[sk.top().x][q];//对q点的f进行记忆
p.x=q;
p.d=d|=a[c[q]];
sk.push(p);
q=0;
}
}
if(f[s]<1000000000)
cout<<f[s]<<endl;
else
cout<<"-1"<<endl;
return 0;
}
练习
1 奇怪的电梯
在N层楼中有一部升降式电梯,电梯间的门上有一个玻璃窗,透过它可以看到电梯所在楼层墙上写着的一个正整数。表示在此层楼按“向上”键或“向下”键能够生或降的层数变化量。电梯中除了这两个键没有别的键可按。
求从A层到B层至少移动过多少层?
1<n<100
2 NOIP提高组2001 CAR的旅行路线
program project1;
type
rec = record
x, y, c: longint;
end;
var
n, tot, costf, st, ed, tc: longint;
pos: array[0..2000] of rec;
x, y: array[1..4] of longint;
costt: array[0..200] of longint;
b: array[0..200] of boolean;
d: array[0..100] of real;
procedure init;
var
i, j, k: longint;
begin
readln(n,costf,st,ed);
for i := 1 to n do
begin
readln(x[1],y[1],x[2],y[2],x[3],y[3],costt[i]);
for j := 1 to 3 do
for k := 1 to 3 do
if j <> k then
if (x[j]-x[k])*(x[6-k-j]-x[j])+(y[j]-y[k])*(y[6-k-j]-y[j]) = 0 then
begin
x[4] := x[k]+x[6-k-j]-x[j];
y[4] := y[k]+y[6-k-j]-y[j];
end;
for j := 1 to 4 do
begin
inc(tot);
pos[tot].x := x[j];
pos[tot].y := y[j];
pos[tot].c := i;
end;
end;
//for i := 1 to tot do writeln(pos[i].x,' ',pos[i].y,' ',pos[i].c);
end;
function dis(x, y: longint):real;
begin
dis := sqrt(sqr(pos[x].x-pos[y].x)+sqr(pos[x].y-pos[y].y));
if pos[x].c = pos[y].c then
dis := dis*costt[pos[x].c]
else dis := dis*costf;
end;
function dij(st: longint):real;
var
i, j, p: longint;
min: real;
begin
fillchar(b,sizeof(b),false);
fillchar(d,sizeof(d),100);
d[st] := 0;
for i := 1 to tot do
begin
min := 1e38;
for j := 1 to tot do
if (not b[j]) and (d[j] < min) then
begin
min := d[j];
p := j;
end;
b[p] := true;
for j := 1 to tot do
if not b[j] then
if d[j] > d[p]+dis(p,j) then d[j] := d[p]+dis(p,j);
end;
dij := 1e38;
for i := 1 to tot do
begin
if pos[i].c = ed then
for j := 0 to 3 do
if d[i+j] < dij then dij := d[i+j];
if pos[i].c = ed then break;
end;
end;
procedure work;
var
i, j: longint;
min, temp: real;
begin
min := 1e38;
for i := 1 to tot do
begin
if pos[i].c = st then
for j := 0 to 3 do
begin
temp := dij(i+j);
if temp < min then min := temp;
end;
if pos[i].c = st then break;
end;
writeln(min:0:1);
end;
begin
assign(input,'d:\in.txt');reset(input);
assign(output,'d:\out.txt');rewrite(output);
readln(tc);
while tc > 0 do
begin
init;
work;
dec(tc);
end;
close(input);
close(output);
end.
3 CCC2009 pencil 买铅笔
4 NOIP提高组2009 最优贸易
5 把海淀竞赛“排雷”用DJS的堆优化方式重新描述
选作NOIP提高组2013DAY2华容道
403

被折叠的 条评论
为什么被折叠?



