1. 程式人生 > 實用技巧 >Solution -「洛谷 P5236」「模板」靜態仙人掌

Solution -「洛谷 P5236」「模板」靜態仙人掌

\(\mathcal{Description}\)

  Link.

  給定一個 \(n\) 個點 \(m\) 條邊的仙人掌,\(q\) 組詢問兩點最短路。

  \(n,q\le10^4\)\(m\le2\times10^4\)

\(\mathcal{Solution}\)

  提出一個環來考慮,從環上一點 \(u\)\(v\),無非兩條路徑。可以按順序處理一個字首和。如圖:

  令 \(sum_2\) 為結點 \(1\)\(2\) 的順時針距離,\(sum_3\) 為結點 \(1\)\(3\) 的順時針距離……特別地,\(sum_1\) 記錄整個環的大小。那麼環上 \(u\)

\(v\) 的最短距離就是 \(\min\{|sum_u-sum_v|,sum_1-|sum_u-sum_v|\}\)。這樣就能 \(\mathcal O(1)\) 求到了。

  接下來建圓方樹(很多題解說建樹的細節與普通圖不一樣,其實正常建也沒有任何問題 qwq),發現一個圓點走進方點(點雙),在走到父親圓點的最短距離可以在 \(\text{Tarjan}\) 演算法中預處理出來。考慮圓方樹上的邊權:圓點到父親方點的邊權為上述最短距離,否則為 \(0\)。對於詢問 \((u,v)\),找到其 \(\text{LCA}\)(設為 \(w\)),分 \(w\) 的情況討論:

  • \(w\) 是圓點,那麼 \(\text{LCA}\)
    時求出的樹上距離就是答案。
  • \(w\) 是方點,此時樹上距離實際上是 \(u\)\(v\) 走到 \(w\) 的父親圓點的距離之和,明顯時錯誤的——\(u\)\(v\) 走進同一個點雙後,需要求一次最短距離而非直接在父親會和。所以可以倍增求到 \(u\) 走進點雙的第一個點(即方點向 \(u\) 點方向的兒子圓點)\(u'\),同理求出 \(v'\),利用處理的字首和求到 \(u'\)\(v'\) 的最短距離。那麼答案就是 \(\operatorname{dist}(u,u')+\operatorname{dist}(u',v')+\operatorname{dist}(v',v)\)

  複雜度 \(\mathcal O(n\log n)\)。不過這裡作者偷懶用 std::map 記錄了每個環的 \(sum\),寫得好看一點是可以在這一部分做到 \(\mathcal O(n)\)。但求走進點雙的第一個點似乎必須用帶 \(\log\) 的演算法 owo?

\(\mathcal{Code}\)

#include <map>
#include <cstdio>
#include <algorithm>

#define adj( g, u, v, c ) \
	for ( int eid = g.head[u], v, c; v = g.to[eid], c = g.cst[eid], eid; eid = g.nxt[eid] )

const int MAXN = 2e4, MAXM = 4e4;
int n, m, q, snode, fa[MAXN + 5][15];
int dfc, top, dfn[MAXN + 5], low[MAXN + 5], stk[MAXN + 5];
int dep[MAXN + 5], dis[MAXN + 5], root[MAXN + 5];
std::map<int, int> sum[MAXN + 5];

struct Graph {
	int ecnt, head[MAXN + 5], to[MAXM + 5], cst[MAXM + 5], nxt[MAXM + 5];
	inline void link ( const int s, const int t, const int c ) {
		to[++ ecnt] = t, cst[ecnt] = c, nxt[ecnt] = head[s];
		head[s] = ecnt;
	}
	inline void add ( const int u, const int v, const int c ) {
		link ( u, v, c ), link ( v, u, c );
	}
} src, tre;

inline char fgc () {
	static char buf[1 << 17], *p = buf, *q = buf;
	return p == q && ( q = buf + fread ( p = buf, 1, 1 << 17, stdin ), p == q ) ? EOF : *p ++;
}

inline int rint () {
	int x = 0; char s = fgc ();
	for ( ; s < '0' || '9' < s; s = fgc () );
	for ( ; '0' <= s && s <= '9'; s = fgc () ) x = x * 10 + ( s ^ '0' );
	return x;
}

inline void wint ( const int x ) {
	if ( 9 < x ) wint ( x / 10 );
	putchar ( x % 10 ^ '0' );
}

inline int min_ ( const int a, const int b ) { return a < b ? a : b; }

inline bool chkmin ( int& a, const int b ) { return b < a ? a = b, true : false; }

inline bool chkmax ( int& a, const int b ) { return a < b ? a = b, true : false; }

inline int cost ( const Graph& g, const int s, const int t ) {
	adj ( g, s, u, c ) if ( u == t ) return c;
	return -1;
}

inline int mncost ( const int id, int u, int v ) {
	int t1 = sum[id][u], t2 = sum[id][v];
	if ( t1 > t2 ) t1 ^= t2 ^= t1 ^= t2;
	return min_ ( t2 - t1, sum[id][root[id]] - ( t2 - t1 ) );
}

inline void buildSquare ( const int rt, const int p, const int sid ) {
	int beg = top; for ( ; stk[beg] ^ p; -- beg );
	root[sid] = rt;
	// loop: rt - stk[beg] - stk[beg + 1] - ... - stk[top] - rt.
	for ( int i = beg, cur = p, pre = rt; i <= top; pre = cur, cur = stk[++ i] ) {
		sum[sid][cur] = sum[sid][pre] + cost ( src, pre, cur );
	}
	sum[sid][rt] = sum[sid][stk[top]] + cost ( src, stk[top], rt );
	int u;
	do {
		u = stk[top --];
		tre.add ( sid, u, mncost ( sid, rt, u ) );
	} while ( u ^ p );
}

inline void Tarjan ( const int u, const int fa ) {
	dfn[u] = low[u] = ++ dfc, stk[++ top] = u;
	adj ( src, u, v, c ) if ( v ^ fa ) {
		if ( ! dfn[v] ) {
			Tarjan ( v, u ), chkmin ( low[u], low[v] );
			if ( low[v] >= dfn[u] ) {
				tre.add ( u, ++ snode, 0 );
				buildSquare ( u, v, snode );
			}
		} else chkmin ( low[u], dfn[v] );
	}
}

inline void init ( const int u, const int f ) {
	dep[u] = dep[fa[u][0] = f] + 1;
	for ( int i = 1; i <= 14; ++ i ) fa[u][i] = fa[fa[u][i - 1]][i - 1];
	adj ( tre, u, v, c ) if ( v ^ f ) {
		dis[v] = dis[u] + c;
		init ( v, u );
	}
}

inline int calcLCA ( int u, int v ) {
	if ( dep[u] < dep[v] ) u ^= v ^= u ^= v;
	for ( int i = 14; ~ i; -- i ) if ( dep[fa[u][i]] >= dep[v] ) u = fa[u][i];
	if ( u == v ) return u;
	for ( int i = 14; ~ i; -- i ) if ( fa[u][i] ^ fa[v][i] ) u = fa[u][i], v = fa[v][i];
	return fa[u][0];
}

inline int climb ( int u, const int tar ) {
	for ( int i = 14; ~ i; -- i ) if ( dep[fa[u][i]] > dep[tar] ) u = fa[u][i];
	return u; 
}

int main () {
	n = snode = rint (), m = rint (), q = rint ();
	for ( int i = 1, u, v, w; i <= m; ++ i ) {
		u = rint (), v = rint (), w = rint ();
		src.add ( u, v, w );
	}
	Tarjan ( 1, 0 );
	init ( 1, 0 );
	for ( int u, v; q --; ) {
		u = rint (), v = rint ();
		int w = calcLCA ( u, v );
		if ( w <= n ) wint ( dis[u] + dis[v] - 2 * dis[w] );
		else {
			int pu = climb ( u, w ), pv = climb ( v, w );
			wint ( dis[u] - dis[pu] + dis[v] - dis[pv] + mncost ( w, pu, pv ) );
		}
		putchar ( '\n' );
	}
	return 0;
}