P1186 玛丽卡
题目非常得不简洁,所以这里还是做一下翻译吧
总的来说,就是给你一个图,有一些无向边,然后有一条边可能会堵车(可以理解为这条路不能走),这条边是任意的。求一个时间,这个时间满足在任意一条路堵车的时候,玛丽卡都可以从起点走到终点
那其实不难发现,这道题的答案就是在少一条边的情况下,求出所有最短路中最长的一条,这其实很好实现,枚举每一条边,使这一条边拥堵,其他边仍然联通,跑最短路,存储答案后再从所有最短路中选一条最大的就行了。到此就可以写出第一个程序(emmm..我这个FW连这个暴力都只有30,他们都有50...)
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e7+50;
const int INF=20040915;
int n,m;
struct node{
int x,y,z;
}a[MAXN];
int head[MAXN],tot;
struct edge{
int net,to,w;
}e[MAXN];
void add(int u,int v,int w){
e[++tot].net=head[u];
e[tot].to=v;
e[tot].w=w;
head[u]=tot;
}
bool v[MAXN];
int d[MAXN],sum,ans[MAXN];
void dij(int s){
priority_queue<pair<int,int> > q;
for(register int i=1;i<=n;i++){
d[i]=INF;
v[i]=false;
}
d[s]=0;
q.push(make_pair(0,s));
while(!q.empty()){
int x=q.top().second;
q.pop();
if(v[x]==true) continue;
v[x]=true;
for(register int i=head[x];i;i=e[i].net){
int y=e[i].to,z=e[i].w;
if(d[y]>d[x]+z){
d[y]=d[x]+z;
q.push(make_pair(-d[y],y));
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(register int i=1;i<=m;i++) scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
for(register int i=1;i<=m;i++){ //枚举任意一条边拥堵的情况
memset(e,0,sizeof e); //记得清零
for(register int j=1;j<=m;j++){ //让其他边正常连通
if(i!=j){
add(a[j].x,a[j].y,a[j].z);
add(a[j].y,a[j].x,a[j].z); //双向边
}
}
dij(1);
ans[++sum]=d[n]; //记录答案
}
int res=-INF;
for(register int i=1;i<=sum;i++){
res=max(res,ans[i]);
}
cout<<res;
return 0;
}
好了,意料之中的超时了。如果要优化,肯定是因为有些边没啥用,所以我们看一看样例的情况,如图(粉色的表示在所有边连通时的最短路),然后我们再来手模一下上面的程序:
1 -> 2 (8) : 21 //改变
1 -> 4 (10) : 9
2 -> 4 (10) : 9
2 -> 5 (1) : 27 //改变
2 -> 3 (9) : 9
3 -> 5 (10) : 9
3 -> 4 (7) : 9
//表示在该边拥堵之后的最短路
这么看来,答案就很显然了。当我们在改变原最短路上的任意边之后,最短路就会改变,这是很显然的,其他边的拥堵完全不会影响到原最短路。我们就可以先跑一遍最短路记录路径,然后让路径拥堵,再跑最短路就可以了
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e7+50; //边要开两倍数组,在保证不会超出限制的情况下,多开也没什么吧
int n,m;
struct node {
int net,to,w,from;
} e[MAXN];
int head[MAXN],tot;
int d[MAXN],v[MAXN];
int bian[MAXN];//记录边
void add(int u,int v,int w) {
e[++tot].net=head[u];
e[tot].to=v;
e[tot].from=u;//这个用来表示前驱节点
e[tot].w=w;
head[u]=tot;
}//链式前向星建边
void dij(int s,int p) {//第一个参数表示起点,第二个参数表示哪一条边拥堵
priority_queue<pair<int,int> >q;
for(register int i=1; i<=n; i++) d[i]=20040915,v[i]=false; //初始化,memset谨慎使用
d[s]=0;
q.push(make_pair(0,s));
while(!q.empty()) {
int x=q.top().second;
q.pop();
if(v[x]==true) continue;
v[x]=true;
for(register int i=head[x]; i; i=e[i].net) {
if(i!=p) { //如果该边没有拥堵
int y=e[i].to,z=e[i].w;
if(d[y]>d[x]+z) {
if(p==-1) bian[y]=i; //第一次跑最短路的时候用来记录路径
d[y]=d[x]+z;
q.push(make_pair(-d[y],y));
}
}
}
}
}
int Max(int x,int y){
if(x>=y) return x;
return y;
}//手打max有些时候会快些(亲身经历)
int main() {
scanf("%d%d",&n,&m);
for(register int i=1; i<=m; i++) {
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}//输入,建双向边
dij(1,-1);//第一次跑最短路没有边拥堵
int ans=d[n],t=n;
while(t){ //枚举最短路上的每一条边,然后更新答案
dij(1,bian[t]);
ans=Max(ans,d[n]);
t=e[bian[t]].from;
}
printf("%d",ans);
return 0;
}