0x60 圖論

0x61 最短路

Dijkstra

最短路的經典作法之一,相比後面的\(SPFA\)\(Dij\)的複雜度更低,更加的穩定node

可是若是出現負環時,\(Dij\)是不能用的c++

#include <bits/stdc++.h>
#define PII pair< int , int >
#define F first
#define S second 
using namespace std;


const int N = 10005 , M = 500005 , INF = 0x7f7f7f7f;
int n , m , s ;
int dis[N];
vector< PII > e[N];
bool vis[N];
set<PII> q;

inline int read()
{
    register int x = 0;
    register char ch = getchar();
    while( ch < '0' || ch > '9' ) ch = getchar();
    while( ch >= '0' && ch <= '9' )
    {
        x = ( x << 3 ) + ( x << 1 ) + ch - '0';
        ch = getchar();
    }
    return x;
}

inline void add( int x , int y , int w ) { e[x].push_back( { y , w } ); }

inline void dijkstra()
{
    memset( dis , INF , sizeof( dis ) ) , dis[s] = 0;
    q.insert( { 0 , s } );
    for( register int u ; q.size() ; )
    {
        u = q.begin()->S , q.erase( q.begin() );
        if( vis[u] ) continue;
        vis[u] = 1;
        for( register auto it : e[u] )
        {
            if( dis[ it.F ] <= dis[u] + it.S ) continue;
            dis[it.F] = dis[u] + it.S;
            q.insert( { dis[it.F] , it.F } );
        }
    }
    return ;
}

int main()
{
    n = read() , m = read() , s = read();
    for( register int i = 1 , x , y , z ; i <= m ; x = read() , y = read() , z = read() , add( x , y , z ) , i ++ );
    dijkstra();
    for( register int i = 1 ; i <= n ; printf( "%d " , ( vis[i] ? dis[i] : 0x7fffffff ) ) , i ++ );
    puts("");
    return 0;
}

爲何\(Dijkstra\)不能處理帶負權邊

不能處理負環很簡單,有負環的狀況下不存在最短路,能夠一直刷呢一個負環git

爲何不能處理負權邊呢?看下面這個圖算法

1是起點,咱們先把1加入堆中,而後開始擴展能夠獲得dis[2] = 1 , dis[3] = 2優化

而後取出2能夠擴展出dis[3] = -1spa

而後取出3能夠擴展出dis[2] = -3,此時就出現錯誤了,由於dij每次從堆頂取出的點必定是最短路已知的點code

可是若是出現負權邊咱們能夠經過這條邊擴展一條已經出隊的點,與dij算法的前提要求衝突,因此dij不能處理帶負權邊的圖blog

SPFA

\(SPFA\)這個名字貌似只有中國人在用,國外通常叫「隊列優化的Bellman-Ford算法」排序

\(SPFA\)算法通常在判斷有無負環的時候用,若是是網格圖的話,不推薦使用\(SPFA\),會很是的慢遞歸

#include <bits/stdc++.h>s
#define PII pair< int , int >
#define F first
#define S second 
using namespace std;


const int N = 1e4+5 , M = 5e5 + 5 ;
const int INF = 0x7fffffff;
int n , m , s ;
int dis[N];
vector< PII > e[N];
queue<int> q;
bool vis[N];



inline int read()
{
    register int x = 0;
    register char ch = getchar();
    while( ch < '0' || ch >'9' ) ch = getchar();
    while( ch >= '0' && ch <= '9' )
    {
        x = ( x << 3 ) + ( x << 1 ) + ch - '0';
        ch = getchar();
    }
    return x;
} 

inline void spfa()
{
    for( register int i = 1 ; i <= n ; i ++ ) dis[i] = INF;
    q.push( s ) , vis[s] = 1 , dis[s] = 0;
    for( register int u ; q.size() ; )
    {
        u = q.front() , q.pop() , vis[u] = 0;
        for( register auto it : e[u] )
        {
            if( dis[u] + it.S >= dis[ it.F ] ) continue;
            dis[ it.F ] = dis[u] + it.S ;
            if( vis[it.F] ) continue;
            q.push( it.F ) , vis[ it.F ] = 1;
        }
    }
    return ;
}


int main()
{
    n = read() , m = read() , s = read();
    for( register int i = 1 , x , y , z ; i <= m ; x = read() , y = read() , z = read() , e[x].push_back( { y , z } ) , i ++ );
    
    spfa();
    
    for( register int i = 1 ; i <= n ; printf( "%d " , dis[i] ) , i ++ );
    return 0;   
}

判負環

說道\(SPFA\)判斷負環,經常使用地作法是判斷每一個點被鬆弛的次數,若是有一個點被鬆弛了\(N\)次,圖中就會有負環

對比Dij

其實就是看數據選擇了,若是沒有負權邊的話,確定是\(Dijkstra\),出現負權邊在考慮是否用\(SPFA\)

這裏在討論一個問題,\(SPFA\)\(Dij\)有什麼本質區別,或者說原理上有什麼區別

\(Dij\)是按照點來擴展,\(SPFA\)是按照邊來擴展

\(Dij\)按照點擴展很好理解,由於他每次都是去找距離最小的點在向外擴展,因此也很好理解爲何要用堆來維護

那麼\(SPFA\)按照邊來擴展,看似沒有道理嗎,確實若是你單看\(SPFA\)你可能會以爲這玩意有什麼用,很暴力的樣子

其實並非,你能夠先了解下\(Bellman-ford\)算法,枚舉每一條邊,擴展這邊所鏈接的點

而後根據後人的研究發現,只有通過鬆弛的點,所連的邊纔有可能會鬆弛其餘的點,因此就有人隊列優化

來儲存鬆弛過的點

在說一點,\(Dij\)算法中堆最大是多少

能夠根據\(Dij\)是按照點連擴展的,因此一個點最多能夠擴展他鏈接的全部的點,故堆最大就是全部點的出度之和

AcWing 340. 通訊線路

二分答案,加\(Dij\)判斷

二分答案,二分第\(k+1\)條邊的權值,小於\(k+1\)的邊不用考慮費用,大於\(k+1\)的邊消耗優惠活動的一個條邊

因此能夠令大於\(k+1\)的邊權值爲\(1\),其餘的邊權值爲\(0\)這樣跑一遍\(Dij\)就能夠統計出有多少條邊大於\(k+1\)條邊這樣就可判斷

其實這道題數據較水\(SPFA\)也能夠直接過

#include <bits/stdc++.h>
#define PII pair< int , int >
#define F first
#define S second
using namespace std;


const int N = 1005 , maxline = 1e6 + 5 , INF = 0x3f3f3f3f;
int n , m , k , l , r = maxline , mid , ans = -1 , dis[N];
bool vis[N];
vector< PII > e[N];
set< PII > s;


inline int read()
{
    register int x = 0;
    register char ch = getchar();
    while( ch < '0' || ch > '9' ) ch = getchar();
    while( ch >= '0' && ch <= '9' )
    {
        x = ( x << 3 ) + ( x << 1 ) + ch -'0';
        ch = getchar();
    }
    return x;
}


inline void add( int x , int y , int z )
{
    e[x].push_back( { y , z } ) , e[y].push_back( { x , z } );
    return ;
}
inline bool panding()
{
    memset( dis , INF , sizeof( dis ) ) , memset( vis , 0 , sizeof( vis ) ) , dis[1] = 0 , s.clear() , s.insert( { 0 , 1 } );   
    for( register int u , cur ; s.size() ; )
    {
        u = s.begin() -> S , s.erase( s.begin() );
        if( vis[u] ) continue;
        vis[u] = 1;
        for( register auto it : e[u] )
        {
            cur = dis[u] + ( ( it.S > mid ) ? 1 : 0 );
            if( dis[it.F] <= cur ) continue;
            dis[it.F] = cur;
            s.insert( { cur , it.F } );
        }
    }
    return dis[n] <= k;
}

int main()
{
    n = read() , m = read() , k = read();
    for( register int i = 1 , x , y , z ; i <= m ; x = read() , y = read() , z = read() , add( x , y , z ) , i ++ );
    while( l <= r )
    {
        mid = ( l + r ) >> 1;
        if( panding() ) ans = mid , r = mid - 1;
        else l = mid + 1;
    } 
    cout << ans << endl;
    return 0;
}

Luogu P2296 尋找道路

\(Noip\)原題,首先考慮如何知足第一個條件,反向建圖,充目標點開始作\(DFS\),把全部到達的點打上標記,這樣若是一個點的子節點所有都被打上標記,那麼這個點就知足第一條件

第二個條件就是裸的最短路,直接在被標記的點的中跑一遍最短路

#include <bits/stdc++.h>
#define PII pair< int, int >
#define F first
#define S second
using namespace std;


const int N = 10005 , INF = 0x7f7f7f7f;
int n , m , st , ed , dis[N];
bool vis[N] , can[N];
set< PII > s;
vector<int> e[N] , t[N] ;


inline int read()
{
    register int x = 0;
    register char ch = getchar();
    while( ch < '0' || ch > '9' ) ch = getchar();
    while( ch >= '0' && ch <= '9' )
    {
        x = ( x << 3 ) + ( x << 1 ) + ch - '0';
        ch = getchar(); 
    } 
    return x;
}

inline void add( int x , int y ) { e[x].push_back(y) , t[y].push_back(x); }

inline void dfs( int x )
{
    can[x] = 1;
    for( register auto v : t[x] )
    {
        if( can[v] ) continue; 
        dfs( v );
    }
    return ;
}

inline bool panding( int x )
{
    for( register auto v : e[x] )
    {
        if( can[v] ) continue;
        return 1;
    }
    return 0;
}

inline void Dijkstra()
{
    for( register int i = 1 ; i <= n ; i ++ ) dis[i] = INF;
    s.insert( { 0 , st } ) , dis[st] = 0;
    
    for( register int u , w; s.size() ; )
    {
        u = s.begin() -> S , s.erase( s.begin() );
        if( vis[u] ) continue;
        vis[u] = 1;
        if( panding(u) ) continue;
        if( u == ed ) return ;
        for( register auto v : e[u] )
        {
            if( dis[v] <= dis[u] + 1 ) continue;
            dis[v] = dis[u] + 1;
            s.insert( { dis[v] , v } );
        }
    }
}


int main()
{
    n = read() , m = read();
    for( register int x , y ; m >= 1 ; x = read() , y = read() , add( x , y ) , m -- );
    st = read() , ed = read();
    
    dfs(ed);
    
    Dijkstra();
    
    cout << ( dis[ed] == INF ? -1 : dis[ed] ) << endl;
    return 0;
}

Loj #2590.最優貿易

f[i]表示1n的路徑上最大值,g[i]表示in的路徑上的最小值

跑兩遍\(Dij\),更新的時候把f[v]= f[u] + w變爲f[v] = max( f[u] , w )便可

最後找到max( f[i] - g[i] )便可

#include<bits/stdc++.h>
#define PII pair< int , int >
#define F first
#define S second
using namespace std;


const int N = 1e5 + 5 , INF = 0x7f7f7f7f;
int n , m , a[N] , f[N] , g[N] , ans;
bool vis[N];
vector< int > e[N] , t[N];
set< PII > s ;

inline int read()
{
    register int x = 0;
    register char ch = getchar();
    while( ch < '0' || ch > '9' ) ch = getchar();
    while( ch >= '0' && ch <= '9' )
    {
        x = ( x << 3 ) + ( x << 1 ) + ch - '0';
        ch = getchar();
    }
    return x;
} 

inline void add( int x , int y , int z )
{
    e[x].push_back(y) , t[y].push_back(x);
    if( !( z & 1 ) ) e[y].push_back(x) , t[x].push_back(y);
    return ;
}

inline void solove()
{
    memset( g , INF , sizeof( g ) ) , g[1] = a[1];
    s.insert( { g[1] , 1 } );
    for( register int u ; s.size() ; )
    {
        u = s.begin() -> S , s.erase( s.begin() );
        if( vis[u] ) continue;
        vis[u] = 1;
        for( register auto v : e[u] )
        {
            g[v] = min( g[u] , a[v] );
            s.insert( { g[v] , v } );
        }
    }
    
    memset( vis , 0 , sizeof( vis ) ) , memset( f , -INF , sizeof( f ) ) , f[n] = a[n];
    s.insert( { f[n] , n } );
    for( register int u ; s.size() ; )
    {
        u = s.begin() -> S , s.erase( s.begin() );
        if( vis[u] ) continue;
        vis[u] = 1;
        for( register auto v : t[u] )
        {
            f[v] = max( f[u] , a[v] );
            s.insert( { f[v] , v } );
        }
    }
    return ;
}


int main()
{
    n = read() , m = read();
    for( register int i = 1 ; i <= n ; a[i] = read() , i ++ );  
    for( register int i = 1 , x , y , z ; i <= m ; x = read() , y = read() , z = read() , add( x , y , z ) , i ++ );
    
    solove();
    
    for( register int i = 1 ; i <= n ; i ++ ) ans = max( ans , f[i] - g[i] );
    cout << ans << endl;
    return 0;
}

AcWing 342. 道路與航線

由於有負權邊,不能直接用\(Dij\),考慮\(SPFA\)\(T\)

觀察題面的性質,只有單向變會出現負值,先不考慮單向變

會發現圖變成了不連通了,可是每一個連通塊內沒有負值了,塊內能夠直接跑\(Dij\)求最短路

而後咱們根據拓撲序逐個跑每一個聯通塊內的最短路,由於拓撲序的性質,能夠避免重複的鬆弛,來保證\(Dij\)的性質

#include<bits/stdc++.h>
#define pb( x ) push_back( x )
#define PII pair< int , int >
#define F first
#define S second
using namespace std;


const int N = 50005 , INF = 0x7f7f7f7f;;
int n , r , p , st , tot , bl[N] , deg[N] , dis[N];
bool vis[N];
queue< int >q ;
vector< int > group[N];
vector< PII > e[N];
set< PII >heap;

inline int read()
{
    register int x = 0 , f = 1;
    register char ch = getchar();
    for( ; ch < '0' || ch > '9' ; ( ch == '-' ? f = - 1 : f ) , ch = getchar() );
    for( ; ch >= '0' && ch <= '9' ; x = ( x << 3 ) + ( x << 1 ) + ch - '0' , ch = getchar() );
    return x * f;
}


inline void add( int x , int y , int z , bool f )
{
    e[x].push_back( { y , z } );
    if( f ) e[y].push_back( { x , z } );
    return ;
}

inline void dfs( int x , int id ) 
{
    bl[x] = id;
    group[id].pb( x );
    for( auto it : e[x] )
    {
        if( bl[it.F] ) continue;
        dfs( it.F , id );
    }
    return ;
}

inline void Dijkstra()
{
    for( register int u ; heap.size() ; )
    {
        u = heap.begin() -> S , heap.erase( heap.begin() );
        if( vis[u] ) continue;
        vis[u] = 1;
        for( register auto it : e[u] )
        {
            if( dis[it.F] > dis[u] + it.S )
            {
                dis[it.F] = dis[u] + it.S;
                if( bl[it.F] == bl[u] ) heap.insert( { dis[it.F] , it.F } );
            }
            if( bl[it.F] != bl[u] )
            {
                deg[ bl[it.F] ] --;
                if( !deg[ bl[it.F] ] ) 
                    q.push( bl[it.F] ); 
            }
        }
    }
    return ;
}


int main()
{
    n = read() , r = read() , p = read() , st = read();         
    for( register int x , y , z ; r >= 1 ; x = read() , y = read() , z = read() , add( x , y , z , 1 ) , r -- );
    for( register int i = 1 ; i <= n ; i ++ )
    {
        if( bl[i] ) continue;
        dfs( i , ++ tot );
    }
    for( register int x , y , z ; p >= 1 ; x = read() , y = read() , z = read() , add( x , y , z , 0 ) , deg[ bl[y] ] ++ , p -- );
    q.push( bl[st] );
    for( register int i = 1 ; i <= tot ; i ++ )
    {
        if( deg[i] ) continue;
        q.push(i);
    }

    for( register int i = 1 ; i <= n ; dis[i] = INF , i ++ ); dis[st] = 0;
    
    for( register int t ; q.size() ; )
    {
        t = q.front() , q.pop();
        for( auto i : group[t] ) heap.insert( { dis[i] , i } );
        Dijkstra();  
    }
    
    for( register int i = 1 ; i <= n ; ( dis[i] >= INF ? puts("NO PATH") : printf( "%d\n" , dis[i] ) ) , i ++ );
    return 0;
}

Floyd

上面給的兩種算法,都只是但源多匯最短路,若是要求多源多匯最短路固然能夠用n\(Dij\)\(SPFA\)來作

但這樣的效果並很差,這時就能夠採用\(Floyd\)算法

for( int i = 1 ; i <= n ; i ++ )
{
    for( int j = 1 ; j <= n ; j ++ )
    {
        for( int k = 1 ; k <= n ; k ++ )
        {
            dis[i][j] = min( dis[i][j] , dis[i][k] + dis[k][j] );
        }
    }
}

Luogu P2910 Clear And Present Danger

由於這題給定的了訪問點的順序,因此咱們不準要考慮順序的問題

跑一邊最短路,統計下給定路徑的權值和便可

#include<bits/stdc++.h>
using namespace std;


const int N = 105 , M = 10005;
int n , m , a[M] , dis[N][N] , sum;


inline int read()
{
    register int x = 0;
    register char ch = getchar();
    while( ch < '0' || ch > '9' ) ch = getchar();
    while( ch >= '0' && ch <= '9' )
    {
        x = ( x << 3 ) + ( x << 1 ) + ch - '0';
        ch = getchar();
    }
    return x;
} 


int main()
{
    n = read() , m = read();
    for( register int i = 1 ; i <= m ; a[i] = read() , i ++ ); a[0] = 1;
    for( register int i = 1 ; i <= n ; i ++ )
    {
        for( register int j = 1 ; j <= n ; dis[i][j] = read() , j ++ );
    }
    
    for( register int i = 1 ; i <= n ; i ++ )
    {
        for( register int j = 1 ; j <= n ; j ++ )
        {
            if( i == j ) continue;
            for( register int k = 1 ; k <= n ; k ++ )
            {
                if( k == i || k == j ) continue;
                dis[i][j] = min( dis[i][j] , dis[i][k] + dis[k][j] ); 
            }
        }
    }
    for( register int i = 1 ; i <= m ; sum += dis[ a[ i - 1 ] ][ a[i] ] , i ++ );
    cout << sum << endl;
    return 0;
}

Loj #10072.Sightseeing Trip

求最小環的經典問題,一邊作\(Floyd\),一邊求min( d[i][j] + d[j][k] + d[k][i] )

可是爲了方便咱們統計咱們保證kikj之間必定有一條邊直接鏈接,這樣就變成了min( d[i][j] + a[j][k] + a[k][i] ),其中a[i][j]表示的是原圖

統計路徑只須要遞歸的去作便可

#include <bits/stdc++.h>
#define LL long long 
using namespace std;


const int N = 310 , INF = 0x3f3f3f3f;
int a[N][N] , d[N][N] , pos[N][N] , n , m , ans = INF + 1 ;
vector< int > path;

inline void get_path( int x , int y )
{
    if( pos[x][y] == 0 ) return ;
    get_path( x , pos[x][y] );
    path.push_back( pos[x][y] );
    get_path( pos[x][y] , y );
}

inline int read()
{
    register int x = 0;
    register char ch = getchar();
    while( ch < '0' || ch > '9' ) ch = getchar();
    while( ch >= '0' && ch <= '9' )
    {
        x = ( x << 3 ) + ( x << 1 ) + ch - '0';
        ch = getchar();
    } 
    return x;
}


int main()
{
    n = read() , m = read();
    memset( a , 0x3f , sizeof( a ) );
    for( register int i = 1 ; i <= n ; i ++ ) a[i][i] = 0;
    for( register int i = 1 , x , y , z ; i <= m ; i ++ )
    {
        x = read() , y = read() , z = read();
        a[x][y] = a[y][x] = min( a[x][y] , z );
    }
    memcpy( d , a , sizeof( d ) );
    for( register int k = 1 ; k <= n ; k ++ )
    {
        for( register int i = 1 ; i < k ; i ++ )
        {
            for( register int j = i + 1 ; j < k ; j ++ )
            {
                if( (LL)d[i][j] + a[j][k] + a[k][i] >= ans ) continue;
                ans = d[i][j] + a[j][k] + a[k][i];
                path.clear();
                path.push_back(i);
                get_path( i , j );
                path.push_back(j);
                path.push_back(k);
            }
        }
        for( register int i = 1 ; i <= n ; i ++ )
        {
            for( register int j = 1 ; j <= n ; j ++ )
            {
                if( d[i][j] <= d[i][k] + d[k][j] ) continue;
                d[i][j] = d[i][k] + d[k][j];
                pos[i][j] = k;
            }
        }
    }
    if( ans == 0x3f3f3f3f + 1 )
    {
        puts("No solution.");
        return 0;
    }
    for( register int i = 0 ; i < path.size() ; i ++ ) printf("%d " , path[i] );
    puts("");
    return 0;
}

0x62 最小生成樹

Kruskal

起初每一個點的都是一個獨立的集合,把邊權從小到達排序,按照邊權枚舉邊,用並查集判斷兩個是否在同一個集合,若是在一個集合就跳過當前邊,反之就聯通這兩個集合

下面是Luogu P3366的代碼

#include <bits/stdc++.h> 
#define PIII pair< int , pair< int , int > > 
#define S second
#define F first
using namespace std;


const int N = 5005;
int n , m , fa[N] , cnt , sum ;
vector< PIII > e;


inline int read()
{
    register int x = 0;
    register char ch = getchar();
    for( ; ch < '0' || ch > '9' ; ch = getchar() ); 
    for( ; ch >= '0' && ch <= '9' ; x = ( x << 3 ) + ( x << 1 ) + ch - '0' , ch = getchar() );
    return x;
}

inline int getfa( int x )
{
    if( fa[x] == x ) return x;
    return fa[x] = getfa( fa[x] );
}

inline void Kruskal()
{
    for( register int i = 1 ; i <= n ; fa[i] = i , i ++ );
    sort( e.begin() , e.end() );    
    register int fx , fy ;
    for( auto it : e )
    {
        fx = getfa( it.S.F ) , fy = getfa( it.S.S );
        if( fx == fy ) continue;
        fa[fx] = fy , cnt ++ , sum += it.F;
        if( cnt == n - 1 ) return ;
    }
    return ;
}


int main()
{
    n = read() , m = read();    
    for( register int i = 1 , x , y , z ; i <= m ; x = read() , y = read() , z = read() , e.push_back( { z , { x , y } } ) , i ++ );
    Kruskal();
    
    ( cnt == n - 1 ? printf( "%d\n" , sum ) : puts( "orz" ) );
    return 0;
}

Luogu P2212 澆地Watering the Fields

首先讀入全部的點,\(O(n^2)\)枚舉全部的點,在枚舉點的過程當中直接判斷掉小於\(C\)的點

而後跑一遍\(Kruskal\)便可

#include <bits/stdc++.h>
#define PII pair< int , int >
#define edge pair< int , PII >
#define F first
#define S second
using namespace std;


const int N = 2005;
int n , c , fa[N] , sum , cnt;
vector< edge > e;
PII node[N];


inline int read()
{
    register int x = 0;
    register char ch = getchar();
    for( ; ch < '0' || ch > '9' ; ch = getchar() );
    for( ; ch >= '0' && ch <= '9' ; x = ( x << 3 ) + ( x << 1 ) + ch - '0' , ch = getchar() );
    return x;
}

inline int getlen( int x , int y ) { return ( node[x].F - node[y].F ) * ( node[x].F - node[y].F ) + ( node[x].S - node[y].S ) * ( node[x].S - node[y].S );}

inline int getfa( int x )
{
    if( fa[x] == x ) return x;
    return fa[x] = getfa( fa[x] );  
}

inline void Kruskal()
{
    for( register int i = 1 ; i <= n ; fa[i] = i , i ++ );
    sort( e.begin() , e.end() );
    
    register int fx , fy;
    for(auto it : e )
    {
        fx = getfa( it.S.F ) , fy = getfa( it.S.S );
        if( fx == fy ) continue;
        fa[fx] = fy , sum += it.F , cnt ++; 
        if( cnt == n - 1 ) return ;
    }
    return ;
}


int main()
{
    n = read() , c = read();
    for( register int i = 1 ; i <= n ; node[i].F = read() , node[i].S = read() , i ++ );
    for( register int i = 1 , d ; i <= n ; i ++)
    {
        for( register int j = i + 1 ; j <= n ; j ++ )
        {
            d = getlen( i , j );
            if( d < c ) continue;
            e.push_back( { d , { i , j } } );
        }
    }

    Kruskal();
    
    cout << ( cnt == n - 1 ? sum : -1 ) << endl;
    return 0;
}

AcWing 346. 走廊潑水節

這道題用了\(Kruskal\)的思想和帶權並查集

首先咱們把說有的邊權進行排序,而後從小到大排序

對於一條邊\((x,y,z)\)

首先這條邊要在徹底圖的最小生成樹上,其次對於點\(x,y\)所在的連通塊中每一個點都要直接有邊相連

\(s(i)\)表示節點\(i\)所在連通塊點的個數,因此對於一條邊\((x,y,z)\)的貢獻就是\([ s(x)\times s(y) + 1 ] \times ( z + 1 )\)

因此用帶權並差集維護一下便可

#include <bits/stdc++.h>
#define LL long long
#define PIII pair< int , pair< int , int > >
#define S second
#define F first
using namespace std;


const int N = 6005;
int n , fa[N] , s[N] , fx , fy;
LL ans;
PIII e[N];


inline int read()
{
    register int x = 0;
    register char ch = getchar();
    for( ; ch < '0' || ch > '9' ; ch = getchar() );
    for( ; ch >= '0' && ch <= '9' ; x = ( x << 3 ) + ( x << 1) + ch - '0' , ch = getchar() );
    return x;
} 
 
inline int getfa( int x )
{
    if( x == fa[x] ) return x;
    return fa[x] = getfa( fa[x] );
}
 
inline void work()
{
    n = read();
    for( register int i = 1 , x , y ,z ; i < n ; x = read() , y = read() , z = read() , e[i] = { z , { x , y } } , i ++ );  
    
    sort( e + 1 , e + n );
    ans = 0;
    for( register int i = 1 ; i <= n  ; fa[i] = i , s[i] = 1 , i ++ );
    for( register int i = 1 ; i < n ; i ++ )
    {
        fx = getfa( e[i].S.F ) , fy = getfa( e[i].S.S );
        ans += (LL)( s[fx] * s[fy] - 1 ) * ( e[i].F + 1 );
        fa[fx] = fy;
        s[fy] += s[fx];
    }
    printf( "%d\n" , ans );
    return ;
}

 
int main()
{
    for( register int T = read() ; T >= 1 ; work() , T -- );
}

0x63 樹的直徑與最近公共祖先

樹的直徑

樹上兩點的距離定義爲,從樹上一點到另外一點所通過的權值

當樹上兩點距離最大時,就稱做樹的直徑,樹的直徑既能夠指這個權值,也能夠指這個路徑

兩遍DFS求樹的直徑

咱們能夠先從任意一點開始\(DFS\),記錄下當前點所能到達的最遠距離,這個點爲\(P\)

在從\(P\)開始\(DFS\)記錄下所能達到的最遠點的距離,這個點爲\(Q\)

\(P,Q\)就是直徑的端點,\(dis(P,Q)\)就是直徑

void dfs( int x )
{
    if( dis[x] > ans ) ans = dis[x] , p = x;
    vis[x] = 1;
    for( int i = head[x] ; i ; i = e[i].next )
    {
        if( e[i].v ) continue;
        dis[ e[i].v ] + e[i].w;
        dfs( e[i].v );
    }
    return ;
}

void dfs2( int x )
{
    if( dis[x] > ans ) ans = dis[x] , q = x;
    vis[x] = 1;
    for( int i = head[x] ; i ; i = e[i].next )
    {
        if( e[i].v ) continue;
        dis[ e[i].v ] + e[i].w;
        dfs( e[i].v );
    }
    return ;
}

void solove()
{
    dfs(1);
    ans = dis[p] = 0;
    memset( vis , 0 , sizeof( dis ) );
    dfs2(p);
    cout << ans << endl
}

兩遍\(DFS\)基本相同,其實兩遍\(BFS\)也能夠作到一樣的效果,這裏就不在贅述

樹形DP求樹的直徑

d[x]表示x所能到達的最遠距離,yx的兒子

顯然d[x] = max( d[y] + w( x , y ) )

那麼x所在點的最長鏈天然就是x所到的最長距離加次長距離,在轉移的過程當中記錄最大值便可

void dp( int x )
{
    v[x] = 1;
    for( int i = head[x] ; i ; i = e[i].next ) 
    {
        if( v[ e[i].v ] ) continue;
        dp( e[i].v );
        ans = max( ans , d[x] + d[y] + e[i].w );
        d[x] = max( d[x] , d[y] + e[i].w );
    }
    return ;
}

Luogu P3629 巡邏

在不創建道路時,咱們須要把每條邊都通過一遍,那麼咱們要走的路程顯然是邊數二倍, 即\(2\times ( n - 1)\)

只修建一條道路時,這條路應建在直徑的兩個端點處,那麼咱們要走的路徑長度即爲\(2\times(n−1)−L\), 其中\(L\)表示樹的直徑

修建第二條道路時,又會造成一個環,那麼若是兩條新道路所成的環不重疊,則答案繼續減少,若重疊,則兩個環重疊部分需經歷兩次, 那麼咱們獲得以下算法 :

  1. 求樹的直徑\(L_1\),將\(L_1\)上的邊權賦值爲\(-1\)
  2. 再求樹的直徑\(L_2\),答案即爲\(2\times n-L_1-L_2\)
#include <bits/stdc++.h>
using namespace std;
const int SIZE = 2e5 + 100;

template<typename _T>
inline void read(_T &s) {
    s = 0; _T w = 1, ch = getchar(); 
    while (!isdigit(ch)) { if (ch == '-') w = -1; ch = getchar(); }
    while (isdigit(ch)) { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); }
    s *= w;
}

template<typename _T> 
inline void write(_T s) {
    if (s < 0) putchar('-'), s = -s;
    if (s > 9) write(s / 10);
    putchar(s % 10 + '0');
}

int n, k, cnt, tot = 1, ans, st, ed;
int nex[SIZE << 1], lin[SIZE], ver[SIZE << 1], dis[SIZE], edge[SIZE], fa[SIZE], f[SIZE];
bool vis[SIZE];
queue <int> q;

inline void add(int from, int to, int dis) {
    ver[++tot] = to;
    nex[tot] = lin[from]; 
    edge[tot] = dis;
    lin[from] = tot;
}

inline void init() {
    read(n); read(k);
    for (int i = 1, x, y; i < n; ++i) {
        read(x), read(y);
        add(x, y, 1), add(y, x, 1);
    }
}

int bfs(int s) {
    memset(vis, false, sizeof(vis));
    int loc = s;
    fa[s] = 0;
    vis[s] = true;
    dis[s] = 0;
    q.push(s);
    while (!q.empty()) {
        int u = q.front(); q.pop();
        for (int i = lin[u]; i; i = nex[i]) {
            int v = ver[i];
            if (!vis[v]) {
                vis[v] = true; q.push(v);
                dis[v] = dis[u] + 1;
                fa[v] = u;
                if (dis[loc] < dis[v]) loc = v;
            }
        }
    }
    return loc;
}

void pre_work()
{
    st = bfs(1); 
    ed = bfs(st);
    bfs(1);
    memset(vis, false, sizeof(vis));
    if (dis[st] < dis[ed]) swap(st, ed);
    vis[st] = vis[ed] = true;
    while (dis[st] > dis[ed]) {
        st = fa[st];
        vis[st] = true;
        ++cnt;
    }
    while (st != ed) {
        st = fa[st];
        ed = fa[ed];
        vis[st] = vis[ed] = true;
        cnt += 2;
    }
}

void sign(int u) {
    for (int i = lin[u]; i; i = nex[i]) {
        int v = ver[i];
        if (v != fa[u]) {
            if (vis[v] && vis[u]) {
                edge[i] = edge[i ^ 1] = -1;
            }
            sign(v);
        }
    }
}

void dp(int u)
{
    int _max = 0;
    for (int i = lin[u]; i; i = nex[i]) {
        int v = ver[i];
        if (v != fa[u]) {
            dp(v);
            ans = max(ans, _max + f[v] + edge[i]);
            _max = max(_max, f[v] + edge[i]);
        }
    }
    ans = max(ans, _max);
    f[u] = _max;
}

inline void output() {
    if (k == 1) {
        write(2 * (n - 1) - cnt + 1), putchar('\n');
        exit(0);
    }
    if (cnt == n - 1) {
        write(n + 1), putchar('\n');
        exit(0);
    }
    sign(1);
    dp(1);
    write(2 * n - cnt - ans), putchar('\n'); 
}

int main() {
    init();
    pre_work();
    output();
    return 0;
}
相關文章
相關標籤/搜索