[BZOJ2407/4398]-探险/福慧双修-最短路+建图

说在前面

me没有去参加开学典礼会不会被老师打啊…


题目

这双倍经验题两个题号都是权限题…emmmm
BZOJ2407传送门
BZOJ4398传送门

2407的题面

探险家小T好高兴!X国要举办一次溶洞探险比赛,获奖者将得到丰厚奖品哦!小T虽然对奖品不感兴趣,但是这个大振名声的机会当然不能错过!
比赛即将开始,工作人员说明了这次比赛的规则:每个溶洞和其他某些溶洞有暗道相连。两个溶洞之间可能有多条道路,也有可能没有,但没有一条暗道直接从自己连到自己。参赛者需要统一从一个大溶洞出发,并再次回到这个大溶洞。
如果就这么点限制,那么问题就太简单了,可是举办方又提出了一个条件:不能经过同一条暗道两次。这个条件让大家犯难了。这该怎么办呢?
到了大溶洞口后,小T愉悦地发现这个地方他曾经来过,他还记得有哪些暗道,以及通过每条暗道的时间。小T现在向你求助,你能帮他算出至少要多少时间才能回到大溶洞吗?

输入输出格式

输入格式:
第一行两个整数N,M,表示点数和边数
接下来M行,每行(u,v,fw,bw)描述一条边,表示该边连通u和v,顺着走时间fw,逆着走时间bw

输出格式:
输出一行一个整数,表示符合条件的最短路


解法

题目要求,最短路不能来回走。一个最简单粗暴且合法的方法就是,先确定走出去的第一条边,然后把回去的路割掉(因为去掉了这种路之后,如果绕路一定不优),之后再跑最短路,对1的每一条出边都这么做一次,显然可以得到最优解。

然而这种做法肯定是要T的。重新思考一下这道题的限制,如果一个与1相邻的点u,它的最短路第一条出边并不是u,那么就可以用dis[u]+length更新答案(即存在1->v->….->u->1这样的路径),而如果u的最短路第一条出边就是u,那么就不能用dis[u]+length去更新答案(显然这时最短路为1->u->1)。但是如果存在另外的某一条路1->v->…->u的话,是可以用 dis[u]+length 更新答案的。记1到u的最短路第一条出边为pre[u],那么合法的最短路一定是形如1->u->v->1且pre[u]!=pre[v]

那么建一张新图,新开节点T,对于原图的每一条单向边(u1,v,len)
如果u是1:如果pre[v] != v,建边(1,v,dis[v]),不然不建边
如果v是1:如果pre[u] != u,建边(1,T,dis[u]+length),不然建边(u,T,length)
如果uv都不是1:如果pre[u] != pre[v],建边(1,v,dis[u]+length),不然建边(u,v,length)
为什么这样建边呢?因为这样的话,新图中每一条从1出发的边,一定是绕过路的,而每一条与终点相连的边,一定都是没有绕过路的。于是新图中1到T的每一条路径,都一定在原图满足出发边和回边不一样(因为只要不是1->u->…->u->1,其余都是合法路径),那么对新图跑最短路,dis[T]就是最终答案

感觉这个题还是挺妙的,但是这个建图方法…貌似比较个例


下面是代码

#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

int N , M , tp , head[40005] , T ;
struct Edge{
    int u , v , fw , bw ;// forward && backward
    void read_(){
        scanf( "%d%d%d%d" , &u , &v , &fw , &bw ) ;
        if( u > v ) swap( u , v ) , swap( fw , bw ) ;
    }
}e[200005] ;
struct Path{
    int pre , to , len ;
}p[400005] ;

inline void In( int t1 , int t2 , int t3 ){
    p[++tp] = ( Path ){ head[t1] , t2 , t3 } ; head[t1] = tp ;
}

int dis[40005] , pre[40005] ;
struct Data{
    int id , dis ;
    bool operator < ( const Data &A ) const {
        return dis > A.dis ;
    }
} ;
priority_queue<Data> que ;
void Dijk( bool UPD ){
    memset( dis , 0x3f , sizeof( dis ) ) ;
    dis[1] = 0 ; que.push( ( Data ){ 1 , 0 } ) ;
    while( !que.empty() ){
        Data u = que.top() ; que.pop() ;
        if( u.dis > dis[u.id] ) continue ;
        for( int i = head[u.id] ; i ; i = p[i].pre ){
            int v = p[i].to ;
            if( dis[v] > u.dis + p[i].len ){
                if( UPD ) pre[v] = ( u.id == 1 ? v : pre[u.id] ) ;
                dis[v] = u.dis + p[i].len ;
                que.push( ( Data ){ v , dis[v] } ) ;
            }
        }
    }
}
/*bool inque[40005] ;
int que[5000005] , fr , ba ;
void Spfa(){
    fr = 1 , ba = 0 ;
    memset( dis , 0x3f , sizeof( dis ) ) ;
    dis[1] = 0 ; que[++ba] = 1 , inque[1] = true ;
    while( fr <= ba ){
        int u = que[fr++] ; inque[u] = false ;
        for( int i = head[u] ; i ; i = p[i].pre ){
            int v = p[i].to ;
            if( dis[v] > dis[u] + p[i].len ){
                pre[v] = ( u == 1 ? v : pre[u] ) ;
                dis[v] = dis[u] + p[i].len ;
                if( !inque[v] ){
                    que[++ba] = v ;
                    inque[v] = true ;
                }
            }
        }
    }
}*/

void solve(){
    Dijk( 1 ) ; tp = 0 ;
    memset( head , 0 , sizeof( head ) ) ;
    for( int i = 1 ; i <= M ; i ++ ){
        int u = e[i].u , v = e[i].v ;
        if( u == 1 ){
            if( pre[v] != v ){
                In( 1 , v , dis[v] ) ;// direction :1 to v 
                In( 1 , T , dis[v] + e[i].bw ) ;// direction :v to 1
            } else In( v , T , e[i].bw ) ;// direction :v to 1 
        } else {
            if( pre[v] == pre[u] ){
                In( u , v , e[i].fw ) ;// direction :u to v 
                In( v , u , e[i].bw ) ;// direction :v to u
            } else{
                In( 1 , v , dis[u] + e[i].fw ) ;// direction :u to v
                In( 1 , u , dis[v] + e[i].bw ) ;// direction :v to u
            }
        }
    } Dijk( 0 ) ;
    printf( "%d" , dis[T] ) ;
}

int main(){
    scanf( "%d%d" , &N , &M ) ; T = N + 1 ;
    for( int i = 1 ; i <= M ; i ++ ){
        e[i].read_() ;
        In( e[i].u , e[i].v , e[i].fw ) ;
        In( e[i].v , e[i].u , e[i].bw ) ;
    }
    solve() ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值