Luogu2176 [USACO14FEB]路障Roadblock 题解
题目链接
Vector存图+Dijkstra最短路
翻了翻题解,貌似没有用vector存图的,既然这样那本蒟蒻就贴一个vector的题解。
题目思路比较简单,先跑一遍正常的最短路,然后枚举在这条最短路上的每一条边,将其长度翻倍,再跑一遍最短路,输出新的最短路减去旧的最短路之后的最大值。
本题关键就是如何记录原先的最短路:
在Dijkstra更新距离d数组时额外记录前驱节点,即记录d[i]的上一步d[j],本操作可以使用结构体实现。
代码块:
//枚举与x点相连的每一条边。
for(register int i=0;i<v[x].size();i++){
// val是路径长,from是前一个节点。
if(d[v[x][i].to].val>d[x].val+v[x][i].val){
d[v[x][i].to].val=d[x].val+v[x][i].val;
d[v[x][i].to].from=x;
q.push(make_pair(d[v[x][i].to].val,v[x][i].to));
//优先队列优化
}
}
由于d数组存的是1号点到所有点的最短路,而我们需要的有效路径只是从1到n的最短路,所以如果将路径记录完毕后将路径提取出来,后面将路径翻倍会相对更容易实现,我们可以利用递归从n点倒着退回来,将有效路径单独保存出来。
inline void Return(int xx){
if(d[xx].from==0)return;//退到了起点则退出,起点不需记录
Return(d[xx].from); //未到起点则再往前退一步
Road_Recorder[++h]=xx; //将非起点路径记录
}
//最后保存出来的路径即为
//1->Road_Recorder[1]->Road_Recorder[2]->……->n
Vector操作核心:将路径长度翻倍
使用vector存图时改变路径长一般用枚举方式实现。若将x点与y点的路径长翻倍,我们需要枚举与x相连的所有边,直至找到y点,将此边改变。由于是双向图,我们需要再重复此操作更改y与x相连的边。由于枚举导致本步时间复杂度并不确定。
由于我们还要再改其他的边,所以我们改完当前边并记录结果之后还需将其还原,所以我们在修改时直接记录下此边在vector中的位置,\(O(1)\)还原。
//last为起点,Road_Recorder[i]为目标点
for(register int j=0;j<v[last].size();j++){ //枚举所有边
if(v[last][j].to==Road_Recorder[i]){//找到了目标边
v[last][j].val<<=1; //更改
change1=j; //记录位置
break;
}
}
//重复进行,将起点与目标点交换
for(register int j=0;j<v[Road_Recorder[i]].size();j++){
if(v[Road_Recorder[i]][j].to==last){
v[Road_Recorder[i]][j].val<<=1;
change2=j;
break;
}
}
路径的还原
v[last][change1].val>>=1;
v[Road_Recorder[i]][change2].val>>=1;
最后贴完整代码
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
struct node{
int to,val;
}add;
vector<node>v[101];
struct Node{
int from,to,val;
}d[101];
bool f[101];
inline void ADD(int x,int y,int z){
add.to=y;
add.val=z;
v[x].push_back(add);
}
int m,n,x,y,z,h,ans,change1,change2,dd[101],Road_Recorder[101],last=1;
inline void Return(int xx){
if(d[xx].from==0)return;
Return(d[xx].from);
Road_Recorder[++h]=xx;
}
int main(){
scanf("%d%d",&n,&m);
for(register int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
ADD(x,y,z);
ADD(y,x,z);
//建图
}
/* 首先跑一边Dij,算出原始最短路 ↓*/
memset(d,0x7f,sizeof(d));
d[1].val=d[1].from=0;
q.push(make_pair(0,1));
while(!q.empty()){
x=q.top().second;
q.pop();
if(f[x])continue;
f[x]=1;
for(register int i=0;i<v[x].size();i++){
if(d[v[x][i].to].val>d[x].val+v[x][i].val){
d[v[x][i].to].val=d[x].val+v[x][i].val;
d[v[x][i].to].from=x;
q.push(make_pair(d[v[x][i].to].val,v[x][i].to));
}
}
}
Return(n);//提取记录的路径
for(register int i=1;i<=h;i++){//枚举改变的路径
for(register int j=0;j<v[last].size();j++){//更改长度
if(v[last][j].to==Road_Recorder[i]){
v[last][j].val<<=1;
change1=j;
break;
}
}
for(register int j=0;j<v[Road_Recorder[i]].size();j++){
if(v[Road_Recorder[i]][j].to==last){
v[Road_Recorder[i]][j].val<<=1;
change2=j;
break;
}
}
/*算出新的最短路↓*/
memset(dd,0x7f,sizeof(dd));
memset(f,0,sizeof(f));
dd[1]=0;
q.push(make_pair(0,1));
while(!q.empty()){
x=q.top().second;
q.pop();
if(f[x])continue;
f[x]=1;
for(register int j=0;j<v[x].size();j++){
if(dd[v[x][j].to]>dd[x]+v[x][j].val){
dd[v[x][j].to]=dd[x]+v[x][j].val;
q.push(make_pair(dd[v[x][j].to],v[x][j].to));
}
}
}
if(dd[n]-d[n].val>ans)ans=dd[n]-d[n].val;//比较答案
v[last][change1].val>>=1;
v[Road_Recorder[i]][change2].val>>=1;//路径还原
last=Road_Recorder[i];//本次的目标点会成为下次的起点
//last初值为1
}
printf("%d",ans);
}
如上文所说,由于枚举导致时间复杂度并不确定,精心造数据的话可能会被卡的慢一些,但是肯定不会TLE吧……(弱弱地)。
Luogu P1186 玛丽卡题目相似,数据范围n<=1000,最慢的点304ms,真的算很快了。平均复杂度貌似\(O(n^2logn)\),反正不会有人故意出测试点卡这种方法。
欢迎各位巨佬提出建议和指教最后谢谢大家能够忍着如此啰嗦的题解看到最后。
~~(逃……~~