说在前面
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() ;
}