最短路的經典作法之一,相比後面的\(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; }
不能處理負環很簡單,有負環的狀況下不存在最短路,能夠一直刷呢一個負環git
爲何不能處理負權邊呢?看下面這個圖算法
1
是起點,咱們先把1
加入堆中,而後開始擴展能夠獲得dis[2] = 1 , dis[3] = 2
優化
而後取出2
能夠擴展出dis[3] = -1
spa
而後取出3
能夠擴展出dis[2] = -3
,此時就出現錯誤了,由於dij
每次從堆頂取出的點必定是最短路已知的點code
可是若是出現負權邊咱們能夠經過這條邊擴展一條已經出隊的點,與dij
算法的前提要求衝突,因此dij
不能處理帶負權邊的圖blog
\(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\)次,圖中就會有負環
其實就是看數據選擇了,若是沒有負權邊的話,確定是\(Dijkstra\),出現負權邊在考慮是否用\(SPFA\)
這裏在討論一個問題,\(SPFA\)和\(Dij\)有什麼本質區別,或者說原理上有什麼區別
\(Dij\)是按照點來擴展,\(SPFA\)是按照邊來擴展
\(Dij\)按照點擴展很好理解,由於他每次都是去找距離最小的點在向外擴展,因此也很好理解爲何要用堆來維護
那麼\(SPFA\)按照邊來擴展,看似沒有道理嗎,確實若是你單看\(SPFA\)你可能會以爲這玩意有什麼用,很暴力的樣子
其實並非,你能夠先了解下\(Bellman-ford\)算法,枚舉每一條邊,擴展這邊所鏈接的點
而後根據後人的研究發現,只有通過鬆弛的點,所連的邊纔有可能會鬆弛其餘的點,因此就有人隊列優化
來儲存鬆弛過的點
在說一點,\(Dij\)算法中堆最大是多少
能夠根據\(Dij\)是按照點連擴展的,因此一個點最多能夠擴展他鏈接的全部的點,故堆最大就是全部點的出度之和
二分答案,加\(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; }
\(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; }
f[i]
表示1
到n
的路徑上最大值,g[i]
表示i
到n
的路徑上的最小值
跑兩遍\(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; }
由於有負權邊,不能直接用\(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; }
上面給的兩種算法,都只是但源多匯最短路,若是要求多源多匯最短路固然能夠用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] ); } } }
由於這題給定的了訪問點的順序,因此咱們不準要考慮順序的問題
跑一邊最短路,統計下給定路徑的權值和便可
#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; }
求最小環的經典問題,一邊作\(Floyd\),一邊求min( d[i][j] + d[j][k] + d[k][i] )
可是爲了方便咱們統計咱們保證k
和i
、k
和j
之間必定有一條邊直接鏈接,這樣就變成了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; }
起初每一個點的都是一個獨立的集合,把邊權從小到達排序,按照邊權枚舉邊,用並查集判斷兩個是否在同一個集合,若是在一個集合就跳過當前邊,反之就聯通這兩個集合
下面是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; }
首先讀入全部的點,\(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; }
這道題用了\(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 -- ); }
樹上兩點的距離定義爲,從樹上一點到另外一點所通過的權值
當樹上兩點距離最大時,就稱做樹的直徑,樹的直徑既能夠指這個權值,也能夠指這個路徑
咱們能夠先從任意一點開始\(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\)也能夠作到一樣的效果,這裏就不在贅述
設d[x]
表示x
所能到達的最遠距離,y
是x
的兒子
顯然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 ; }
在不創建道路時,咱們須要把每條邊都通過一遍,那麼咱們要走的路程顯然是邊數二倍, 即\(2\times ( n - 1)\)。
只修建一條道路時,這條路應建在直徑的兩個端點處,那麼咱們要走的路徑長度即爲\(2\times(n−1)−L\), 其中\(L\)表示樹的直徑
修建第二條道路時,又會造成一個環,那麼若是兩條新道路所成的環不重疊,則答案繼續減少,若重疊,則兩個環重疊部分需經歷兩次, 那麼咱們獲得以下算法 :
#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; }