描述:
Gabiluso是他国家最厉害的间谍之一。现在他试图去完成一个“不可能”的任务-减缓Colugu市的军队抵达机场的速度。Colugu市有n个车站和m条路。每条路直接连接两个车站,所有的路都是单向的。为了维护空气质量,政府停用了所有军队车辆。所以军队必须乘坐巴士去机场。两个车站之间可能不只一条路。如果一个车站被摧毁,那么所有通向那个车站的道路都没用了。Gabiluso需要去做的是摧毁一些车站使得军队不能在k分钟内赶到机场。一辆巴士通过一条路只需要一分钟。从1到n给所有车站编号。编号为1的车站在军营里,编号为n的车站在机场里。军队总是从编号为1的车站出发。
由于有重兵把守,所以编号为1和n的车站不能被摧毁。当然那里没有一条路直接从1号车站连接到n号车站。
请帮助Gabiluso计算他需要摧毁车站的最小数量,他必须完成任务。
输入:
有多组测试数据。当输入三个0时,输入停止。
对于每组测试数据:
第一行包含三个整数,n,m 和 k。 (0 < n <= 50,0 < m <= 4000, 0 < k < 1000)
紧接着有m行,每行包含两个整数 s 和 f,意思是有一条路从s号车站通往f号车站。
输出:
对于每组测试数据,输出 Gabiluso 必须摧毁车站的最小数量。
思路:
按照本题作者陈峰宏的说法,此题拥有网络流的解法,但对于这种数据范围,直接搜索即可。
本题思路部分源于《ACM 国际大学生程序设计竞赛亚洲区预选赛真题题解》,源自于作者但未高于作者。很大一部分原因是书中代码的排版方式有着非人类的可读性,所以严格意义上来说我只看懂了一部分。
下面代码的大体思路是这样的:利用深搜遍历各种车站删除方案,但并不是盲目的搜,而是在1号到n号车站的最短路径上搜索,不断摧毁最短路径上的车站。
最短路径和最短路径的长度由一个广搜来完成。其中包含一个剪枝操作,当路径长度大于k时,直接返回当前值即可。因为我们只需要关心当前路径是否大于k,而不需要具体知道大于k多少。
另外深搜也是逐步递进的,先看看只摧毁1个车站的情况下是否可行,不可行再搜只摧毁2个车站的方案,以此类推。书中作者的原话:“虽然这样看似重复搜索了很多遍,但实际上搜索的深度是有限的,因此搜索的总范围不会非常大。”
代码:
#include<iostream> #include<stdio.h> #include<vector> using namespace std; #define MAXN 50 vector<int>map[MAXN+1]; //邻接表 bool del[MAXN+1],mark[MAXN+1][MAXN+1]; //del[]用于记录某个车站是否被删除 //题目中说明了两个车站之间可以由多条路相连,但也说明了若一个车站被摧毁,那么连接它的路就没有用了。 //因此,多余的路没有用,我们需要用mark[][]记录一条边是否已经输入。如已被输入,便不再加入到邻接表中。 int n,m,k; //n个车站,m条路,k分钟 struct point //将队列元素封转在结构体中 { int num; //车站编号 int pre; //父节点在队列中的位置 }queue[MAXN+1]; void init() //初始化 { for(int i=1;i<=MAXN;i++) { for(int j=1;j<=MAXN;j++) mark[i][j]=false; map[i].clear(); del[i]=false; } } void make_path(int path[],point end,int& stations) //利用end回溯并在path[]里记录下1号车站到n号车站之间的路径。 { stations=0; while(end.pre!=1) { end=queue[end.pre]; path[stations++]=end.num; } } int bfs(int path[],int& stations) //计算从1号车站到2号车站的最短路径 { bool visit[MAXN+1]; //是否访问过 for(int i=1;i<=MAXN;i++) visit[i]=false; int step=0,head=1,tail=1; //step从1号车站到n号车站的最短距离。 //now队列头元素所在位置。 //tail队列尾部元素所在位置。 queue[tail++].num=1; visit[1]=true; while(tail-head) { int size=tail-head; while(size--) { int front=queue[head].num; if(front==n) { make_path(path,queue[head],stations); return step; } for(int i=map[front].size()-1;i>=0;i--) { int u=map[front][i]; if(!visit[u]&&!del[u]) { visit[u]=true; queue[tail].num=u; queue[tail++].pre=head; } } head++; } step++; if(step>k) //我们只需要知道距离是否大于k即可,不需要知道具体大多少。 return step; } return k+1; //1号车站无法通往n号车站。返回一个大于k的值即可。 } int dfs(int total,int range) //total表示已经摧毁车站的数量。 //range表示准许摧毁车站的数量。 { if(total>range) return MAXN; int path[MAXN+1],stations; //path[]用于记录当前状态下的最短路径。 //stations表示最短路径中除1号车站和n号车站外的车站数量。 if(bfs(path,stations)>k) return total; int min=MAXN; //记录最少摧毁车站的数量。 for(int i=0;i<stations;i++) { del[path[i]]=true; //先标记为摧毁 int step=dfs(total+1,range); //此处的step表示需要删除车站的数量。 min=min<step?min:step; del[path[i]]=false; //取消标记 } return min; } int solve() { for(int i=1;i<=n-2;i++) //先检有无可疑只摧毁1个车站的方案,再检查有无摧只毁2个车站的方案,逐步加深。 { int res=dfs(0,i); if(res<=i) return res; } } int main() { while(scanf("%d%d%d",&n,&m,&k)) { init(); if(n==0&&m==0&&k==0) break; for(int i=0,s,f;i<m;i++) { scanf("%d%d",&s,&f); if(!mark[s][f]) { mark[s][f]=true; map[s].push_back(f); } } printf("%d\n",solve()); } }