【洛谷】P3953 逛公园

2 篇文章 0 订阅
1 篇文章 0 订阅

类似于最短路计数让我们想到 Dp

首先 K <= 50,看到这个想到 Dp 是二维的,定义 Dp(i,k)为 i 到终点路径长度比最短路多 k 的路径总数

然后就不会转移了

然后怎么转移呢,Dp(u,k)与 Dp(i,k)的联系,:

最好,建立一个虚拟节点,因为到终点可以往回走

Minpath(i)表示 i 到终点的距离

那么走 w 这条边比最短路多的就是 Minpath(v)+ W(i)- Minpath(u)

转移到 k 就可以用 k - (Minpath(v)+ W(i)- Minpath(u))转移

判 0 环用 ins 判状态是否被重复访问(有 0 才会重复访问),如果这个状态已经被访问过了,那么直接 GG 

最后答案为:

\sum _{i=0}^{k} dp[1][i]

注意初值,dp [ n + 1 ] [ 0 ] = 1 , dp [ n + 1 ] [ 1 ~ k ] = 0 , dp [ 1 ~ n ] [ 0 ~ k ] = - 1

只要有一次 Dfs 有无穷多种,直接输出 - 1,否则累加

 

// luogu-judger-enable-o2
# include <bits/stdc++.h>

const  int  N = 500000 + 5 , K = 50 + 5 ;

int  head [ N ] , nxt [ N << 1 ] , to [ N << 1 ] , w [ N << 1 ] , cn ;
int  headv [ N ] , nxtv [ N << 1 ] , tov [ N << 1 ] , wv [ N << 1 ] , cnv ;
int  dp [ N ] [ K ] , dis [ N ] , n , inf = 1e9 + 7 , mod , k , T , m , x , y , z , ans ;
bool  vis [ N ] , ins [ N ] [ K ] ;

void  create ( int  u , int  v , int  d ) {
    cn ++ ;
    w [ cn ] = d ;
    to [ cn ] = v ;
    nxt [ cn ] = head [ u ] ;
    head [ u ] = cn ;
}

void  create_v ( int  u , int  v , int  d ) {
    cnv ++ ;
    wv [ cnv ] = d ;
    tov [ cnv ] = v ;
    nxtv [ cnv ] = headv [ u ] ;
    headv [ u ] = cnv ;
}

std :: queue < int >  q ;

void  spfa ( ) {
    memset ( dis , 0x3f , sizeof ( dis ) ) ;
    memset ( vis , false , sizeof ( vis ) ) ;
    memset ( ins , false , sizeof ( ins ) ) ;
    q . push ( n + 1 ) ;
    dis [ n + 1 ] = 0 ;
    while ( ! q . empty ( ) ) {
        int  u = q . front ( ) ; q . pop ( ) ; vis [ u ] = false ;
        for ( int  i = headv [ u ] ; i ; i = nxtv [ i ] ) {
            int  v = tov [ i ] ;
            if ( dis [ v ] > dis [ u ] + wv [ i ] ) {
                dis [ v ] = dis [ u ] + wv [ i ] ;
                if ( ! vis [ v ] ) {
                    q . push ( v ) ;
                    vis [ v ] = true ;
                }
            }
        }
    }
}

int  dfs ( int  u , int  k ) {
    if ( ins [ u ] [ k ] )  return  inf ;
    if ( dp [ u ] [ k ] != - 1 )  return  dp [ u ] [ k ] ;
    dp [ u ] [ k ] = 0 ;
    ins [ u ] [ k ] = true ;
    for ( int  i = head [ u ] ; i ; i = nxt [ i ] ) {
        int  v = to [ i ] ;
        int  delta = dis [ u ] - w [ i ] - dis [ v ] + k ;
        if ( delta < 0 )  continue ;
        if ( dfs ( v , delta ) == inf )  return  dp [ u ] [ k ] = inf ;
        dp [ u ] [ k ] = ( dp [ u ] [ k ] + dp [ v ] [ delta ] ) % mod ;
    }
    ins [ u ] [ k ] = false ;
    return  dp [ u ] [ k ] ;
}

void  init ( ) {
    cn = cnv = ans = 0 ;
    memset ( head , 0 , sizeof ( head ) ) ;
    memset ( headv , 0 , sizeof ( headv ) ) ;
    memset ( dp , - 1 , sizeof ( dp ) ) ;
    dp [ n + 1 ] [ 0 ] = 1 ;
    for ( int  i = 1 ; i <= k ; i ++ )
        dp [ n + 1 ] [ i ] = 0 ;
}

int  main ( ) {
    
    scanf ( "%d" , & T ) ;
    
    while ( T -- ) {
        scanf ( "%d%d%d%d" , & n , & m , & k , & mod ) ;
        
        init ( ) ;
        
        for ( int  i = 1 ; i <= m ; i ++ )
            scanf ( "%d%d%d" , & x , & y , & z ) , create ( x , y , z ) , create_v ( y , x , z ) ;
        
        create ( n , n + 1 , 0 ) ;
        create_v ( n + 1 , n , 0 ) ;
        
        spfa ( ) ; 
        
        for ( int  i = 0 ; i <= k ; i ++ ) {
            if ( dfs ( 1 , i ) == inf ) {
                ans = - 1 ;
                break ;
            }
            ans = ( ans + dp [ 1 ] [ i ] ) % mod ;
        }
        
        printf ( "%d\n" , ans ) ;
    }
    
    
    
    return  0 ; 
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值