问题描述:
在Z国和W国之间一直战火不断。好不容易,W国的间谍把完整的Z国的军事基地的地图到手了。于是W国决定再次出击,一举击
破Z 国的防线。
W国认真研究了Z国的地形,发现Z国有N个军事基地,我们不妨编号成1..N,而且经过深刻研究,发现1号军事基地是资源补
给基地,而N号军事基地是前线。由于地形的缘故,只有M对军事基地两两可达,当然是有距离的。此时W国的弹头紧缺,当下的弹
头只能去毁灭一个军事基地。当然了,最重要的就是毁灭一个军事基地,使得资源补给基地与前线的最短距离发生变化。但是Z 国
也不是白痴,他们的资源补给基地与前线有着极高的防御力,所以W 国只能去炸掉其余的N-2个基地,当然炸掉某个基地后,这个
基地就不可达了。于是问题就来了,炸掉哪些基地后会使得资源补给基地与前线的最短距离发生变化呢?注:假若炸掉某个基地后
,1号基地和N号基地不连通,那么我们也认为他们的最短距离发生了变化。
输入格式(destroy.in ):
输入数据第一行是两个正整数N,M,意义如题所述。
接下来M行,每行包括三个正整数x,y,d,表示有一条边连接x、y两个地点,其距离为d。数据保证图是连通的。
输出格式(destroy.out):
输出数据的第一行包含一个整数K,表示有K个基地可毁灭,且毁灭其中任意一个后,资源补给基地与前线的最短距离发生变化
。接下来K行,每行输出一个军事基地的编号,要求编号递增。
在wyl8899神犇的率领下,W国必胜!!!
因此一定不会存在K=0的情况。
输入样例:
6 7
1 2 3
1 5 2
2 3 5
2 4 3
2 5 4
2 6 5
3 4 2
输出样例:
1
2
解题:
首先,先了解题目的大意:要求求出所有从点1 到点n 的最短路必定经过的点。
所以,开始想能否直接gfs ,如果可以,那么答案的求法为:
设有两条最短路,7个点:
第1 条最短路:途经的点为1011011(第 i 个点为1代表有经过第 i 点,反之则为 0 );
第2 条最短路:途经的点为1001001
若第 i 点在所有路中都为1 ,那么 i 就是一个答案。
接着,数据中 n 为10000,m为100000,所以肯定不能裸裸的广搜,深搜就更不可以了— —! 毕竟要不断的搜,不断
的更新最短路径。也就是说不能用搜索去求最短路,但求最短路还是有很多的方法的,狄杰斯科拉等等。本题解采用狄杰斯科
拉。毕竟很无脑。。很好打。。Orz。。最短路有什么用下文会说。。
最后,就是分析了,之前之所以不能裸裸的广搜原因是什么?经过分析很容易发现是因为有一堆完全没有用处的边,占据
了大量的空间和时间。所以就要考虑是否能去掉这些无用的边,答案就是构造最短路可拓扑图,设求出的点1 到第 i 个点的最
短距离为 d[ i ] ,那么对于给出的所有路中,如果存在给出的边(a,b,c),使得d[ a ] + c = d[ b ] 那么这条边就是有价值
的!这是个很可喜的发现,因为这意味着:针对该题,完全可以使用搜索,只不过把搜索的范围限定在最短路经过的边!
算法实现:
先求出第一个点到其它点的最短路
从第n 个点(终点)开始倒着搜索:
对于每个点,它所能到达的那些点中,只拓展满足上诉的有价值边。
也就是说,利用预处理过的点1 到个点的最短路、来判定从点1 到点n 的最短路中经过的点,具体看代码
代码:
1 #include<cstdio> 2 int n,m,x,y,d,t=0,mindis[10010],tot=0,num[10010];//mindis存点1到各点的最短距离,num[i]为所有最短路中经过i点的次数 3 int wei[10010],las[200010],too[200010],dis[200010],check[10010]; //前四个位哈希数组,后一个为dijkstra的判定数组 4 void link(int x,int y) { las[++t]=wei[x]; wei[x]=t; too[t]=y; dis[t]=d;}//将一条边添加进哈希数组 5 void sear(int b) { //从b点拓展 6 for (int i=wei[b]; i; i=las[i]) //链表枚举能到的点 7 if (mindis[too[i]]+dis[i]==mindis[b]) //判定是否为有价值的边,是才执行 8 if (too[i]==1) tot++; else { //如果走到了起点那么方案加一 9 num [too[i]]++; //经过too[i]点的次数加一 10 sear(too[i]) ; //拓展该有价值的点 11 } 12 } 13 void print(int x,int t) { //这是输出,从第n个点开始枚举 14 if (x>1) { 15 if (num[x]==tot) t++; //一个点是答案的前提是经过次数等于方案数,如果相等,总点数+1 16 print(x-1,t); //因为x>1,代表没扫完,所以继续扫前一个点 17 if (num[x]==tot) printf("%d ",x);//输出该点 18 } else printf("%d ",t); //如果扫到了第一个点,就输出总必经点数 19 } //采取递归,以便满足输出顺序为:方案数》》从小到大个点 20 int main(){ 21 scanf ("%d%d" ,&n ,&m ); //读入数据,连接双向边 22 for (int i=1; i<=m; i++) { 23 scanf("%d%d%d",&x,&y,&d); 24 link(x,y); link(y,x); 25 } 26 for (int i=0 ; i<=n; i++ ) mindis[i] = 899999999; //初始化到所有点的最小距离 27 for (int i=wei[1]; i ; i=las[i]) mindis[too[i]]=dis[i]; //开始dijkstra 28 for (int i=1 ; i< n; i++ ) { // 29 int f=0; // 30 for (int j=1 ; j<=n; j++ ) // 31 if ((!check[j]) && mindis[f]>mindis[j]) f=j; // 32 check[f]=1; //这片可以无视,只要求得出点1到各点的最短路 33 for (int j=wei[f]; j;j=las[j]) //并存在mindis数组内都ok 34 if (mindis[too[j]]>mindis[f]+dis[j]) // 35 mindis[too[j]]=mindis[f]+dis[j]; // 36 } //结束dijkstra 37 mindis[1]=0; sear(n); print(n-1,0); //深搜,输出结果 38 } //啦啦啦38行—— ——!
后记:
lazycal巨神告诉我要用spfa。。测试机好的话狄杰斯科拉没事,但不好的话还是会T。。。所以要用spfa~~Orz
END。