CF147B Smile House

Solution 1

\(dp_{s,i,j}\) 表示走了不超过 \(s\) 条边,\(i \to j\) 的最长路径(走不通为极小值)

如果有 \(u \to v\) 的一条边,那么 \(dp_{1,u,v}=w\)\(w\)为这条边的权值)

转移为:

\[dp_{s,i,j}=\max_{1 \le k \le n}\{dp_{s-1,i,k}+dp_{1,k,j}\} \]

显然,答案为使得 \(∃i \in [1,n],\ dp_{k,i,i}>0\) 的最小 \(k\)

时间复杂度 \(\mathcal O(n^4)\)

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;

const int MAXN = 300;
int n , m , x , y , z , w , Ans;
int dp[ MAXN + 5 ][ MAXN + 5 ][ MAXN + 5 ];

int main( ) {
    memset( dp , 0xcf , sizeof( dp ) );

    scanf("%d %d",&n,&m);
    for( int i = 1 ; i <= m ; i ++ ) {
        scanf("%d %d %d %d",&x,&y,&z,&w);
        dp[ 1 ][ x ][ y ] = z;
        dp[ 1 ][ y ][ x ] = w;
    }
    for( int s = 2 ; s <= n ; s ++ ) {
        for( int k = 1 ; k <= n ; k ++ )
            for( int i = 1 ; i <= n ; i ++ ) {
                for( int j = 1 ; j <= n ; j ++ )
                    dp[ s ][ i ][ j ] = max( dp[ s ][ i ][ j ] , dp[ s - 1 ][ i ][ k ] + dp[ 1 ][ k ][ j ] );
                if( dp[ s ][ i ][ i ] > 0 ) {
                    Ans = s;
                    goto there;
                }
            }     
    } 
    there:;
    printf("%d",Ans);
    return 0;
}

Solution 2

我们有必要重新定义一下 \(dp\) ,设 \(dp_{s,i,j}\) 表示走了不超过 \(2^s\) 条边,\(i \to j\) 的最长路径(走不通为极小值)。

转移为:

\[dp_{s,i,j}=\max_{1 \le k \le n}\{dp_{s-1,i,k}+dp_{s-1,k,j}\} \]

可以使用倍增 \(\mathcal O(n^3 \log_n)\) 求得。

在我们计算答案时,我们用类似倍增爬树的方法。

先从小到大尝试不超过 \(2^s\) 条边的路径是否有正环。

1.如果没有正环,说明答案大于\(2^s\) , 那么下一步增加\(2^{s-1}\)条边

转移时需用到上一次状态,需额外记录上一次尝试 \(i \to j\) 的最长路径。

2.如果有正环,说明答案小于等于\(2^s\),那么下一步尝试\(2^{s-1}\)条边

总时间复杂度\(\mathcal O(n^3\log_n)\)

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;

const int MAXN = 300 , MAXK = 10;
int n , m , x , y , z , w , Ans;
int dp[ MAXK + 5 ][ MAXN + 5 ][ MAXN + 5 ];
int tmp[ MAXN + 5 ][ MAXN + 5 ] , now[ MAXN + 5 ][ MAXN + 5 ];

int main( ) {
    memset( now , 0xcf , sizeof( now ) );
    memset( dp , 0xcf , sizeof( dp ) );

    scanf("%d %d",&n,&m);
    for( int i = 1 ; i <= m ; i ++ ) {
        scanf("%d %d %d %d",&x,&y,&z,&w);
        dp[ 0 ][ x ][ y ] = z;
        dp[ 0 ][ y ][ x ] = w;
    }
    for( int i = 1 ; i <= n ; i ++ )
        now[ i ][ i ] = 0 , dp[ 0 ][ i ][ i ] = 0;
    
    for( int s = 1 ; s <= MAXK ; s ++ )		//预处理
        for( int k = 1 ; k <= n ; k ++ )
            for( int i = 1 ; i <= n ; i ++ )
                for( int j = 1 ; j <= n ; j ++ )
                    dp[ s ][ i ][ j ] = max( dp[ s ][ i ][ j ] , dp[ s - 1 ][ i ][ k ] + dp[ s - 1 ][ k ][ j ] );
    
    for( int s = MAXK ; s >= 0 ; s -- ) {
        memset( tmp , 0xcf , sizeof( tmp ) );

        for( int k = 1 ; k <= n ; k ++ )	//转移
            for( int i = 1 ; i <= n ; i ++ )
                for( int j = 1 ; j <= n ; j ++ )
                    tmp[ i ][ j ] = max( tmp[ i ][ j ] , now[ i ][ k ] + dp[ s ][ k ][ j ] );

        bool f = 0;
        for( int i = 1 ; i <= n ; i ++ )
            if( tmp[ i ][ i ] > 0 ) {
                f = 1;
                break;
            }
        if( f ) continue;	//有正环
        else {				//无正环
            for( int i = 1 ; i <= n ; i ++ )
                for( int j = 1 ; j <= n ; j ++ )
                    now[ i ][ j ] = tmp[ i ][ j ];
            Ans += 1 << s;
        }
    }
    printf("%d\n", Ans >= n ? 0 : Ans + 1 );
    return 0;
}
posted @ 2021-04-08 21:32  chihik  阅读(42)  评论(0编辑  收藏  举报