P神的SDFZ考试题 C题

时间:2023-03-09 07:06:18
P神的SDFZ考试题 C题

                                                                  探险
【问题描述】
          探险家小 T 好高兴! X 国要举办一次溶洞探险比赛,获奖者将得到丰厚奖品
  哦!小 T 虽然对奖品不感兴趣,但是这个大振名声的机会当然不能错过!
  比赛即将开始,工作人员说明了这次比赛的规则:每个溶洞和其他某些溶洞
  有暗道相连。两个溶洞之间可能有多条道路,也有可能没有,但没有一条暗道直
  接从自己连到自己。参赛者需要统一从一个大溶洞出发,并再次回到这个大溶洞。
  如果就这么点限制,那么问题就太简单了,可是举办方又提出了一个条件:
  不能经过同一条暗道两次。这个条件让大家犯难了。这该怎么办呢?
  到了大溶洞口后,小 T 愉悦地发现这个地方他曾经来过,他还记得有哪些
  暗道,以及通过每条暗道的时间。小 T 现在向你求助,你能帮他算出至少要多
  少时间才能回到大溶洞吗?(起点编号为 1)
【输入格式】
  第一行两个数 n , m 表示溶洞的数量以及暗道的数量。
  接下来 m 行,每行 4 个数 s 、 t 、 w 、 v,表示一个暗道连接的两个溶洞 s 、 t,
  这条暗道正着走(s à t)的所需要的时间 w,倒着走(t à s)所需要的时间 v。
  由于溶洞的相对位置不同,w 与 v 可能不同。
【输出格式】
  输出一行一个数 t,表示最少所需要的时间。
【样例输入】

3 3

1 2 2 1

2 3 4 5

3 1 2 3

【样例输出】
    8

这又是一道bzoj的权限题

首先这个题可以打一个暴力的写法:

枚举从一号节点出发第一个走的溶洞,再强行把那条边断开,然后求那个溶洞到一号节点的最短路相加

好像这样是能过的样子

正解有一个极其鬼畜的建图根本看不懂(其实就算看懂了以后也想不出来)

于是P神还有一个一句话嘴巴AC的正解

在spfa的时候维护到一个点的最短路和次短路并且保证最短路和次短路的第一条边不一样,

然后再枚举和1相连的边回去即可

(优先考虑最短路+这条边,如果发现最短路的第一条边和这条边相同,就用次长边+这条边(次长边和最长边的第一条边不同)

Orz Orz跪地膜啊,简单粗暴

但不过这个东西的if打得是真的操蛋啊

其实理清楚了,也就是想写数学大题的分类讨论吧

附上巨丑代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1000050;
const int Inf=20010411;
int gi(){
int x=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x;
}
int cnt=1,n,m,head[N],to[N],nxt[N],c[N],in[N],vis[N],ans=Inf,vis2[N];
int W[N];
struct Data{
int first,second,ffrom,sfrom;
}dis[N];
struct data{
int x,fa,from;
}q[N*2];
void lnk(int x,int y,int v,int z){
to[++cnt]=y,c[cnt]=v,nxt[cnt]=head[x],head[x]=cnt;
to[++cnt]=x,c[cnt]=z,nxt[cnt]=head[y],head[y]=cnt;
}
void spfa(){
int t=0,sum=1;q[0].x=1;dis[1].first=dis[1].second=0;
while(t<sum){
data now=q[t++];
for(int i=head[now.x];i;i=nxt[i]){
if(i!=(now.fa^1)||now.fa==0){
int y=to[i],flag=0;
/*//我们不妨定义xf,xs,yf,ys表示这四条路径长度的鬼畜关系
//先确定长度后再转移
//首先我们知道 xf<xs和yf<ys;
//下面把所有情况列举下来:
//1.xf<yf<xs<ys
//2.xf<yf<ys<xs
//3.xf<xs<yf<ys
//4.xf<xs<yf<ys
//-------------------
//5.yf<xf<xs<ys
//6.yf<xf<ys<xs
//7.yf<ys<xf<xs*/
if(now.x!=1){
if(dis[now.x].first+c[i]<dis[y].first){
//当前更新的路比y的最短路还要短,那么y的最短路一定会被更新,次短路可能会更新
if(dis[y].first<dis[now.x].second+c[i]){
//xf<yf<(xs,ys未知)
if(dis[y].ffrom!=dis[now.x].ffrom){
//xf和yf的第一条边不同,又已知yf和ys的第一条边不会一样
//那么xs不需要考虑
//转移:ys=yf,yf=xf
dis[y].second=dis[y].first;dis[y].sfrom=dis[y].ffrom;
dis[y].first=dis[now.x].first+c[i];dis[y].ffrom=dis[now.x].ffrom;
flag=1;
}
else{
//xf和yf的第一条边相同,yf必定要变成xf的,我们要考虑xs怎么变
//1.xf<yf<ys<xs,那么ys保持不变
if(dis[y].second<dis[now.x].second){
dis[y].first=dis[now.x].first+c[i];
dis[y].ffrom=dis[now.x].ffrom;
flag=1;
}
//2.xf<yf<xs<ys
else{
//如果yf和xs的第一条边不同
//yf=xf,ys=xs;
if(dis[y].ffrom!=dis[now.x].sfrom){
dis[y].first=dis[now.x].first+c[i];
dis[y].ffrom=dis[now.x].ffrom;
dis[y].second=dis[now.x].second+c[i];
dis[y].sfrom=dis[now.x].sfrom;
flag=1;
}
}
}
}
else{//xf<xs<yf<ys,这么好打的一个我竟然忘记了
//这个最理想了直接yf=xf,ys=xs
dis[y].first=dis[now.x].first+c[i];
dis[y].ffrom=dis[now.x].ffrom;
dis[y].second=dis[now.x].second+c[i];
dis[y].sfrom=dis[now.x].sfrom;
}
}
else {//现在是yf<xf
//yf<(xf<xs,ys)未知,那么yf是不能被更新了吧
//1.yf<xf<ys<xs;
if(dis[y].second<dis[now.x].second+c[i]&&dis[y].second>dis[now.x].first+c[i]){
if(dis[y].ffrom!=dis[now.x].ffrom){//yf和xf的第一条边没有冲突,ys=xf;
dis[y].second=dis[now.x].first+c[i];
dis[y].sfrom=dis[now.x].ffrom;
flag=1;
}
}
//2.yf<ys<xf<xs 根本不需要更新
//3.yf<xf<xs<ys;
else if(dis[y].second>dis[now.x].second+c[i]){
if(dis[y].ffrom!=dis[now.x].ffrom){//优先考虑xf来更新
dis[y].second=dis[now.x].first+c[i];
dis[y].sfrom=dis[now.x].ffrom;
flag=1;
}
else if(dis[y].ffrom!=dis[now.x].sfrom){
dis[y].second=dis[now.x].second+c[i];
dis[y].sfrom=dis[now.x].sfrom;
flag=1;
}
}
}
}
if(flag||now.x==1){
q[sum++]=(data){y,i};
}
}
}
}
}
int main(){
freopen("exp.in","r",stdin);
freopen("exp.out","w",stdout);
n=gi(),m=gi();
for(int i=1;i<=m;i++){
int x=gi(),y=gi(),v=gi(),z=gi();
lnk(x,y,v,z);
}
for(int i=2;i<=n;i++) dis[i].first=dis[i].second=Inf;
for(int i=head[1];i;i=nxt[i]){
if(dis[to[i]].first==Inf){
dis[to[i]].first=c[i];
dis[to[i]].ffrom=i;
}
else{
if(dis[to[i]].first>c[i]){
dis[to[i]].second=dis[to[i]].first;
dis[to[i]].sfrom=dis[to[i]].ffrom;
dis[to[i]].first=c[i];
dis[to[i]].ffrom=i;
}
else{
if(dis[i].second>c[i]){
dis[to[i]].second=c[i];
dis[to[i]].sfrom=i;
}
}
}
}
spfa();
for(int i=head[1];i;i=nxt[i]){
int y=to[i];
if(dis[y].ffrom!=i) ans=min(ans,dis[y].first+c[i^1]);
else ans=min(ans,dis[y].second+c[i^1]);
}
printf("%d",ans);
}