从图中指定的一点出发走到某一目标点如果存在多种不同的走法,
最短的是哪条路?其长度是多少?
图论中解决上述问题的方法都属于最短路算法。由于图的特点不同、
存储结构不同、确立算法的侧重方向不同,所以算法是多种多样的。
一 无权图及树网
在无权图中,路径长度只与路径上的点数有关,而与路径上的边权和点权无关。
例如广搜那节课例题2“方格图上求从S到E的最短路”一题。
不少多维(多分量)的图(包括许多抽象图)的最短路都属于此类。
由于广搜就是逐层展开的,所以很适合解决此类问题。
例题1 跳棋
某种跳棋共有5个格子排成一排,
其中两格放黑子、两格放白子,还有一格为空。
跳棋规则允许紧邻空格的棋子走入空格,还允许把一个棋子隔着另一个棋子跳入空格。
求把棋子的一种分布状态变成另一种分布状态至少经过多少步?
先输入一个长度5且含有两个1、两个2、一个0的数字串,表示原来的分布状态,
再输入另一个长度5且含有两个1、两个2、一个0的数字串,表示后来的分布状态。
输出就是最少步数。
分析
把5个位置看成“5维空间(5个分量决定)中的点”,问题就是求从出发点到目标点的最少步数了。
由于图中的点可能被重复搜索,所以需要标记搜过的点。(哈希登记)
又由于每个点的步数都可以从其上游一点的步数加1求得,所以应当建立所有点的步数表。
事实上,上述两个表可以合二而一。参考代码如下:
#include
#include
#include
#include
using namespace std;
int s[3][3][3][3];
string a,b,c;
queue
void bfs()
{
inti,k;
q.push(a);
s[a[0]-'0'][a[1]-'0'][a[2]-'0'][a[3]-'0']=0;
for(i=0;i<4;++i)b[i]-='0';
while(!q.empty()&& s[b[0]][b[1]][b[2]][b[3]]<0)
{
a=q.front();//取出队头元素
k=a.find("0");
for(i=0;i<5;++i)
if(k!=i&& abs(k-i)<3) //i不是空的点,且i和k距离不太大,可跳或可走
{
c=a;
swap(c[k],c[i]);
if(s[c[0]-'0'][c[1]-'0'][c[2]-'0'][c[3]-'0']<0)
{
q.push(c);
s[c[0]-'0'][c[1]-'0'][c[2]-'0'][c[3]-'0']=s[a[0]-'0'][a[1]-'0'][a[2]-'0'][a[3]-'0']+1;
}
}
q.pop();
}
}
int main()
{
memset(s,-1,sizeof(s));
cin>>a>>b;
bfs();
cout<
return0;
}
由于在树状结构中,任意两点间的路径是唯一的,因此,广搜可以求得最短路。
它既是最短路也是最长路。
练习
1 倒米
3个储存米的容器容量分别是3、5、8升,
如果分别放有1、2、5升米,最少倒几次米能评分为两个4升?倒几次能倒成2、5、1升?
输入第一行3个整数分别代表3、5、8升容器中原有米数,
第二行3个整数分别代表3、5、8升容器中希望的米数。
如果倒得成就输出最少倒米次数,否则输出-1.
样例输入
0 2 7
3 1 5
输出 2
样例说明:
0 2 7倒成 0 1 8 倒成 3 1 5
2 九宫格游戏
3行3列9个格子中分别放着1到8的8个数字另有一个空格。
每次可把紧邻空格的数字移入空格,至少移动多少次才能变为目标分布?
如果无法实现输出-1,否则输出最少移动次数。
输入3个长度为3的数字串表示原来的分布,
在输入3个长度为3的数字串表示后来的分布。
0表示空格。
3 NOIP提高组2002 字串变换
4 NOIP提高组2007 树网的核
5 海淀2013 拉灯
拉灯游戏 lamp.cpp|c|pas
n盏灯从左到右放成一排,每盏灯由一个拉线开关来控制。
开始灯都未亮,求在满足下面拉灯规则的前提下,把所有灯都拉亮的最少拉灯线次数。
规则
拉第i盏灯的灯线必须同时满足下面两个前提条件:
① 若i>1,则第i-1盏灯必须是灭着的。
② 若i>2,则号数不大于i-2的灯都是亮着的。
输入文件名为lamp.in中仅有一个正整数n,代表灯盏总数。
输出文件lamp.out中也仅有一个整数表示最少拉灯线次数。
输入输出样例
lamp.in
3
lamp.out
5
样例说明:
原来3盏灯的状态 灭灭灭
拉1灯变成 亮灭灭
拉3灯变成 亮灭亮
拉1灯变成 灭灭亮
拉2灯变成 灭亮亮
拉1灯变成 亮亮亮
数据范围:
80%的数据满足n<20
90%的数据满足n<30
100的数据满足n<100
选作NOIP2013DAY2 华容道
二 可拓扑图
1 直接计算 例如NOIP普及组2004 花生采摘
2 动归 例如跳马、飞翔、传递物品等
最长单调子序列的平方算法也可看做此类算法,它自然而然就是一个拓扑序列,
所以按拓扑顺序动归很容易进行。
由于无圈,所以地推关系得以实现。
例题2 海淀2010竞赛 限体力上楼梯
三 上楼梯 程序文件主名stairs
明明上n级台阶可用4种步幅,当然每种步幅花费的体力也不一样,对应关系如下:
步幅大小 体力花费
1 1
2 3
3 6
4 10
明明开始共有m个体力,求他最少要跨多少步才能上完所有台阶?
输入文件名为stairs.in。其中只有n和m两个正整数,中间用空格做间隔符。0
对于30%的数据,m<100
对于60%的数据,m<10000
对于80%的数据,m<1000000
对于100%的数据,m<10^19
输出文件名为stairs.out,其内容只有题目所求的最少步数这一个数。
输入输出样例
stairs.in的内容为:
3 5
则stairs.out的内容为:
2
分析 此图显然拓扑,而且属于最短路类型。
可设f[i][j]为用j个体力上i级台阶的最少步数
#include
#include
using namespace std;
ifstream fin("stairs.in");
ofstream fout("data.out");
//f[louti][tili]=最少步数
int f[4010][10000];
int main()
{
int i,j,n,m,k,s;
memset(f+1,127,sizeof(f)-40000);
fin>>n>>m;
s=n+1;
intt[5]={0,1,3,6,10};
for(i=0;i
for(j=i;j
for(k=1;k<=4;k++)
if(f[i][j]+1
f[i+k][j+t[k]]=f[i][j]+1;
for(j=n;j<=m;j++)
s=min(s,f[n][j]);
fout<
return 0;
}
练习
6 1变n的最小代价
如果整数c=a*b,
则c可变成c+a,代价是b。
c也可变成c+b,代价是a。
求从1变成n的最小总代价。
输入正整数n,n<200000
输出最小总代价。
7 NOIP提高组2009 乌龟棋
8 NOIP提高组2005 过河
9 跳马三问
在一个很大(坐标不限、可正可负)的棋盘上,马从A行B列出发,
只许向右跳(右上或右下不限),
求跳到C行D列最少跳多少部?最多多少部?共有多少种不同跳法?
输入A、B、C、D4个整数,
10 三维导弹拦截
bomb
题目描述
一场战争正在A国与B国之间如火如荼的展开。
B国凭借其强大的经济实力开发出了无数的远程攻击导弹,B国的领导人希望,通过这些导弹直接毁灭A国的指挥部,从而取得战斗的胜利!当然,A国人民不会允许这样的事情发生,所以这个世界上还存在拦截导弹。
现在,你是一名A国负责导弹拦截的高级助理。
B国的导弹有效的形成了三维立体打击,我们可以将这些导弹的位置抽象三维中间的点(大小忽略),为了简单起见,我们只考虑一个瞬时的状态,即他们静止的状态。
拦截导弹设计非常精良,可以精准的引爆对方导弹而不需要自身损失,
但是A国面临的一个技术难题是,这些导弹只懂得直线上升。
精确的说,这里的直线上升指xyz三维坐标单调上升。
给所有的B国导弹按照1至n标号,一枚拦截导弹可以打击的对象可以用一个xyz严格单调上升的序列来表示,例如:
B国导弹位置:(0, 0, 0) (1, 1, 0) (1, 1, 1), (2, 2, 2)
一个合法的打击序列为:{1, 3, 4}
一个不合法的打击序列为{1, 2, 4}
A国领导人将一份导弹位置的清单交给你,并且向你提出了两个最简单不过的问题(假装它最简单吧):
求1枚拦截导弹最多可以摧毁多少B国的导弹?
不管是为了个人荣誉还是国家荣誉,更多的是为了饭碗,你,都应该好好的把这个问题解决掉!
输入文件
第一行一个整数n给出B国导弹的数目。
接下来n行每行三个非负整数Xi, Yi, Zi给出一个导弹的位置,你可以假定任意两个导弹不会出现在同一位置。
输出文件
输出文件仅有1行。输出一个整数P,表示一枚拦截导弹之多能够摧毁的导弹数。
样例输入
4
0 0 0
1 1 0
1 1 1
2 2 2
样例输出
3
数据约定
所有的坐标都是[0,106]的整数
对于30%的数据满足n 31
对于50%的数据满足n 101
对于100%的数据满足n 1001