图中异色点对最短距离(最小生成树+线段树)
problem
給定一個 nnn 個點,mmm 條邊的無向連通圖,圖有邊權,每個點有一個顏色。
有 qqq 次操作,每次操作可更改某一個點顏色。
求每次操作后圖中不同顏色點之間的最短距離。
若圖中點顏色全相同,輸出 000。
一行給定 n,m,c,qn,m,c,qn,m,c,q。接下來 mmm 行形如 (u,v,w)(u,v,w)(u,v,w),表示 u,vu,vu,v 間有一條邊長 www。
接下來一行 nnn 個數表示顏色。最后 qqq 行,每行形如 x,yx,yx,y,把 xxx 點顏色改為 yyy。
c≤n≤2e5,m≤3e5,q≤2e5,1≤w≤1e6c\le n\le 2e5,m\le 3e5,q\le 2e5,1\le w\le 1e6c≤n≤2e5,m≤3e5,q≤2e5,1≤w≤1e6。
solution
observation:因為 www 是正的,所以答案一定是某條邊的權值。
observation:這條邊一定在圖的隨便一棵 MST\text{MST}MST(最小生成樹)上。
只要發現了這兩個性質,這道題就轉化成了求樹上任意兩異色相鄰點的最短距離。
我們考慮每個點對答案的貢獻,即求出這個點兒子中距離最近且與之異色的兒子。
怎么快速求得?
-
將該點的所有兒子扔到線段樹上,線段樹合并顏色信息。
具體而言,如果兩段相鄰顏色不同,合并后顏色賦零,否則賦成同樣顏色。
并且兒子要按照距離該點的距離有序排列重編號后再以重編號建線段樹。
直接在線段樹上走與該點顏色不同的區間,先走左兒子(距離最近)。
動態開點哦~
然后將所有點對答案的貢獻放到一個優先隊列里面,最短距離就從這中間產生。
現在考慮修改某個點顏色后的變化。顯然只會影響該點的貢獻以及該點父親的貢獻。
-
該點貢獻。直接在其對應線段樹上重新以新顏色為標準找即可。
-
該點父親貢獻。直接在父親對應線段上修改該點的信息,然后父親再查一遍。
-
將兩者產生的新貢獻直接覆蓋原來的貢獻。
具體而言:可以開一個數組記錄每個點實時貢獻,最后輸出最短距離時候判斷一下存的貢獻和對應點現在提供的貢獻是否相同。不同扔掉即可。
時間復雜度就只帶個 log?\loglog 。
code
#include <bits/stdc++.h> using namespace std; #define maxn 200005 #define maxm 300005 #define inf 0x3f3f3f3f #define Pair pair < int, int > struct edge { int u, v, w; }E[maxm]; vector < Pair > G[maxn]; priority_queue < Pair, vector < Pair >, greater < Pair > > ans; int n, m, C, Q, cnt; int c[maxn]; int head[maxn], to[maxn], nxt[maxn], len[maxn]; int f[maxn], dis[maxn], siz[maxn], id[maxn];int find( int x ) {return x == f[x] ? x : f[x] = find( f[x] ); }void addedge( int u, int v, int w ) {len[cnt] = w, to[cnt] = v, nxt[cnt] = head[u], head[u] = cnt ++; }void dfs( int u, int fa ) {f[u] = fa;for( int i = head[u];~ i;i = nxt[i] )if( to[i] ^ fa ) {dfs( to[i], u );G[u].push_back( make_pair( len[i], to[i] ) );siz[u] ++;}sort( G[u].begin(), G[u].end() );for( int i = 1;i <= siz[u];i ++ ) id[G[u][i - 1].second] = i;//根據排序 對于u而言 id越小的兒子離u越近//所以在線段樹上盡可能往左走 }int root[maxn]; namespace sgt {struct node { int lson, rson, color; }t[maxn * 30];#define lson t[now].lson#define rson t[now].rson#define mid (l + r >> 1)void pushup( int now ) {if( ! lson ) t[now].color = t[rson].color;else if( ! rson ) t[now].color = t[lson].color;else {if( t[lson].color ^ t[rson].color ) t[now].color = 0;else t[now].color = t[rson].color;}}void modify( int &now, int l, int r, int pos, int col ) {if( ! now ) now = ++ cnt;if( l == r ) { t[now].color = col; return; }if( pos <= mid ) modify( lson, l, mid, pos, col );else modify( rson, mid + 1, r, pos, col );pushup( now );}int query( int now, int l, int r, int col ) {if( t[now].color == col ) return inf;if( l == r ) return l;if( t[lson].color ^ col ) return query( lson, l, mid, col );//先找左else return query( rson, mid + 1, r, col ); } }int main() {scanf( "%d %d %d %d", &n, &m, &C, &Q );for( int i = 1, u, v, w;i <= m;i ++ ) {scanf( "%d %d %d", &u, &v, &w );if( u == v ) continue;E[i] = (edge){ u, v, w };}for( int i = 1;i <= n;i ++ ) scanf( "%d", &c[i] );sort( E + 1, E + m + 1, []( edge x, edge y ) { return x.w < y.w; } );iota( f + 1, f + n + 1, 1 );memset( head, -1, sizeof( head ) );for( int i = 1;i <= m;i ++ ) {int u = E[i].u, v = E[i].v, w = E[i].w;int fu = find( u ), fv = find( v );if( fu ^ fv ) {f[fv] = fu;addedge( u, v, w );addedge( v, u, w );}}dfs( 1, 0 ); cnt = 0;//對每個點 將其所有兒子建成一棵線段樹 上面儲存顏色信息for( int i = 2;i <= n;i ++ ) sgt :: modify( root[f[i]], 1, siz[f[i]], id[i], c[i] );for( int i = 1;i <= n;i ++ )if( siz[i] ) {int w = sgt :: query( root[i], 1, siz[i], c[i] );if( w == inf ) dis[i] = inf;else dis[i] = G[i][w - 1].first, ans.push( make_pair( dis[i], i ) );//兒子重編號是1~siz[i]對應vector的下標都要-1}for( int i = 1, x, y;i <= Q;i ++ ) {scanf( "%d %d", &x, &y );c[x] = y;if( siz[x] ) {//改和兒子間的答案int w = sgt :: query( root[x], 1, siz[x], c[x] );if( w == inf ) dis[x] = inf;else if( dis[x] != G[x][w - 1].first ) {dis[x] = G[x][w - 1].first;ans.push( make_pair( dis[x], x ) );}}if( f[x] ) {//改和父親的答案sgt :: modify( root[f[x]], 1, siz[f[x]], id[x], c[x] ); //在父親的線段樹上修改x的信息int w = sgt :: query( root[f[x]], 1, siz[f[x]], c[f[x]] );if( w == inf ) dis[f[x]] = inf;else if( dis[f[x]] != G[f[x]][w - 1].first ) {dis[f[x]] = G[f[x]][w - 1].first;ans.push( make_pair( dis[f[x]], f[x] ) );}}while( ! ans.empty() ) {//有些點顏色改變信息不再正確 通過一直是正確的dis來彈出錯誤備選答案if( dis[ans.top().second] ^ ans.top().first ) ans.pop();else break;}if( ans.empty() ) puts("0");else printf( "%d\n", ans.top().first );}return 0; }總結
以上是生活随笔為你收集整理的图中异色点对最短距离(最小生成树+线段树)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Excel工作表的基本操作excel对工
- 下一篇: AtCoder 2305 [AGC010