0w1

CFR 147 B. Smile House ( Binary Search, Fast Matrix Multiplication, DP, Binary Enumeration )

Problem - B - Codeforces

題意:
給一個圖,邊用四個值表示,( x, y, Cxy, Cyx ),代表 x 至 y 長度是 Cxy,y 至 x 長度是 Cyx。求是否存在一個正環,若存在則輸出最小環的長度。

資料規模:
節點數 1 ≤ N ≤ 300
邊數 0 ≤ M ≤ N * ( N - 1 ) / 2
權值 -1e4 ≤ C ≤ 1e4
保證所有邊 i != j
時限 3000 ms
記憶體 256 MB

解法:
首先,可以用 floyd-warshall 的一種 dp 形式
dp[ i ][ j ][ k ] : 經過 i 個邊後,j 至 k 的最大可能距離
由於轉移式為
dp[ i ][ j ][ k ] = max{ dp[ i - 1 ][ j ][ x ] + dp[ 1 ][ x ][ k ] | j != x and x != k }
符合矩陣轉移需要的性質( 這樣的轉移矩陣特別稱作 Min-plus-Matrix ),可以用矩陣快速冪進行加速。考慮 I ( 單位矩陣 ),如果全部的元素都設為負無限,雖然和定義相符,但也就會不滿足二分搜的性質,ex: 互質的環長度。但如果所有 dp[ t ][ i ][ i ] 都設為 0,那最後當結果的矩陣中任一個 dp[ t ][ i ][ i ] 大於 0 時,便代表存在 t 個邊以內的正環。
如果直接這麼做,會被卡的很妙 ( Before.cpp ),必須將一個 lg N 的複雜度。
考慮二進制枚舉,預處理每一個小於 N ( 最大可能環長度 ) 的 2 的冪次的矩陣,從最大的位數考慮,看是否有沒有必要用它。舉例來說,若 N = 300,可以分別考慮 [ 1, 2, 4, 8, 16, 32, 64, 128, 256 ] 個邊形成的轉移矩陣,先枚舉 256 不用,其他全部都用的情形,是否可以形成環。如果可以,代表答案小於 256,否則大於255 ( 或無解 )。下一個考慮如果沒有 128 是否可以形成環,判斷時加入比 128 大且已被判斷必須有的矩陣,和比 128 小的所有矩陣,依此類推。考慮完所有位元後,再做一次額外的判定確認是否存在環。

時間 / 空間複雜度:
O( N ^ 3 lg N )

閒話:
二進制枚舉其實就是廣義的二分搜,但沒想到還能方便預處理,好題目。

Before:

int N, M;
vvi edge;

void init(){
    cin >> N >> M;
    edge = vvi( N, vi( N, -INF ) );
    for( int i = 0; i < N; ++i )
        edge[ i ][ i ] = 0;
    for( int i = 0; i < M; ++i ){
        int u, v, wgo, wback;
        cin >> u >> v >> wgo >> wback;
        --u, --v;
        edge[ u ][ v ] = wgo;
        edge[ v ][ u ] = wback;
    }
}

vvi mat_mul( vvi a, vvi b ){
    vvi res( a.size(), vi( b[ 0 ].size(), -INF ) );
    for( int i = 0; i < a.size(); ++i )
        for( int j = 0; j < a[ 0 ].size(); ++j )
            for( int k = 0; k < b[ 0 ].size(); ++k ) if( a[ i ][ j ] != -INF and b[ j ][ k ] != -INF )
                upmax( res[ i ][ k ], a[ i ][ j ] + b[ j ][ k ] );
    return res;
}

vvi mat_pow( vvi a, int p ){
    vvi res( a.size(), vi( a.size(), -INF ) );
    for( int i = 0; i < a.size(); ++i )
        res[ i ][ i ] = 0;
    while( p ){
        if( p & 1 )
            res = mat_mul( res, a );
        p >>= 1, a = mat_mul( a, a );
    }
    return res;
}

void solve(){
    int ans = 0;
    int lb = 1, rb = N;
    while( lb <= rb ){
        int mid = lb + rb >> 1;
        vvi x = mat_pow( edge, mid );
        int ok = 0;
        for( int i = 0; i < N; ++i )
            ok |= x[ i ][ i ] > 0;
        if( ok )
            ans = mid, rb = mid - 1;
        else
            lb = mid + 1;
    }
    cout << ans << endl;
}

After:

typedef tuple< int, int, int, int > t4i;

int N, M;
vector< t4i > C;

void init(){
    cin >> N >> M;
    C = vector< t4i >( M );
    for( int i = 0; i < M; ++i ){
        int x, y, cxy, cyx;
        cin >> x >> y >> cxy >> cyx;
        C[ i ] = make_tuple( x - 1, y - 1, cxy, cyx );
    }
}

int maxp;
vvvi dbl; // doubled path matrix : shortest path matrix for 2 ^ i number of edges path only
vvvi pdbl; // dot product of first i doubled path matrices

vvi I; // identity matrix

vvi mat_mul( vvi a, vvi b ){
    vvi res = I;
    for( int i = 0; i < a.size(); ++i )
        for( int j = 0; j < b.size(); ++j )
            for( int k = 0; k < b[ 0 ].size(); ++k )
                upmax( res[ i ][ k ], a[ i ][ j ] + b[ j ][ k ] );
    return res;
}

void preprocess(){
    I = vvi( N, vi( N, -INF ) );
    for( int i = 0; i < N; ++i )
        I[ i ][ i ] = 0;
    maxp = 32 - __builtin_clz( N )  - 1;
    dbl = pdbl = vvvi( maxp + 1, vvi( N, vi( N ) ) );
    dbl[ 0 ] = I;
    for( int i = 0; i < M; ++i ){
        int x, y, cxy, cyx;
        tie( x, y, cxy, cyx ) = C[ i ];
        dbl[ 0 ][ x ][ y ] = cxy;
        dbl[ 0 ][ y ][ x ] = cyx;
    }
    for( int i = 0; i < maxp; ++i )
        dbl[ i + 1 ] = mat_mul( dbl[ i ], dbl[ i ] );
    pdbl[ 0 ] = I;
    for( int i = 0; i < maxp; ++i )
        pdbl[ i + 1 ] = mat_mul( pdbl[ i ], dbl[ i ] );
}

void solve(){
    int ans = 0;
    vvi pre = I;
    for( int i = maxp; 0 <= i; --i ){
        int ok = 0;
        vvi cur = mat_mul( pre, pdbl[ i ] );
        for( int j = 0; j < N; ++j )
            ok |= cur[ j ][ j ] > 0;
        if( not ok )
            ans |= 1 << i,
            pre = mat_mul( pre, dbl[ i ] );
    }
    int ok = 0;
    for( int i = 0; i < N; ++i )
        ok |= pre[ i ][ i ] > 0;
    if( not ok )
        cout << 0 << endl;
    else
        cout << ans << endl;
}