1. 程式人生 > 實用技巧 >Solution -「SDOI 2018」「洛谷 P4606」戰略遊戲

Solution -「SDOI 2018」「洛谷 P4606」戰略遊戲

\(\mathcal{Description}\)

  Link.

  給定一個 \(n\) 個點 \(m\) 條邊的無向連通圖,\(q\) 次詢問,每次給出一個點集 \(s\),求至少在原圖中刪去多少個點,使得 \(s\) 中存在兩點不連通。多組資料。

  每組資料 \(n,q\le10^5\)\(m,\sum|s|\le2\times10^5\)

\(\mathcal{Solution}\)

  看到 \(\sum|s|\) 的限制,不難聯想到虛樹或者其它與 DFN 相關的演算法。

  所以,先建出圓方樹,並處理好 DFN,LCA 的一系列資訊。考慮到答案就是 \(s\) 中的點在樹上構成的連通塊中不在 \(s\)

集合裡的圓點個數,可以求一個樹上字首和:\(sum_u\) 表示從 \(u\) 到根路徑上的圓點個數。處理詢問時,先將 \(s\) 按 DFN 從小到大排序,兩兩 LCA 計算貢獻(\(s_1\)\(s_{|s|}\) 也要算一次),最後除以 \(2\),得到 \(cnt\)。不過整個連通塊的根(\(\operatorname{LCA}(s_1,s_{|s|})\))這個點的貢獻被遺漏了,所以要再加上這個點單獨的貢獻。最終答案就是 \(cnt-|s|\)

\(\mathcal{Code}\)

  就是練練碼力的一道題嘛 qwq。

#include <queue>
#include <cstdio>
#include <algorithm>

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

const int MAXN = 2e5, MAXM = 4e5;
int n, m, q, snode;
int dfc, top, dfn[MAXN + 5], low[MAXN + 5], stk[MAXN + 5];
int lg[MAXN * 2 + 5], dep[MAXN + 5], st[MAXN * 2 + 5][20], sum[MAXN + 5];

struct Graph {
	int ecnt, head[MAXN + 5], to[MAXM + 5], nxt[MAXM + 5];
	inline void link ( const int s, const int t ) {
		to[++ ecnt] = t, nxt[ecnt] = head[s];
		head[s] = ecnt;
	}
	inline void add ( const int u, const int v ) {
		link ( u, v ), link ( v, u );
	}
	inline void clear () {
		ecnt = 0;
		for ( int i = 1; i <= n << 1; ++ i ) head[i] = 0;
	}
} src, tre;

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

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

inline void clear () {
	src.clear (), tre.clear ();
	dfc = top = snode = 0;
	for ( int i = 1; i <= n << 1; ++ i ) dfn[i] = low[i] = 0;
}

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

inline void initDFN ( const int u, const int f ) {
	dep[st[dfn[u] = ++ dfc][0] = u] = dep[f] + 1, sum[u] = sum[f] + ( u <= n );
	adj ( tre, u, v ) if ( v ^ f ) initDFN ( v, u ), st[++ dfc][0] = u;
}

inline void initST () {
	for ( int i = 2; i <= n << 2; ++ i ) lg[i] = lg[i >> 1] + 1;
	for ( int j = 1; 1 << j <= dfc; ++ j ) {
		for ( int i = 1; i + ( 1 << j ) - 1 <= dfc; ++ i ) {
			if ( dep[st[i][j - 1]] < dep[st[i + ( 1 << j >> 1 )][j - 1]] ) st[i][j] = st[i][j - 1];
			else st[i][j] = st[i + ( 1 << j >> 1 )][j - 1];
		}
	}
}

inline int calcLCA ( int u, int v ) {
	if ( dfn[u] > dfn[v] ) u ^= v ^= u ^= v;
	int k = lg[dfn[v] - dfn[u] + 1];
	return dep[st[dfn[u]][k]] < dep[st[dfn[v] - ( 1 << k ) + 1][k]] ?
			st[dfn[u]][k] : st[dfn[v] - ( 1 << k ) + 1][k];
}

inline void solve () {
	static int cnts, s[MAXN + 5];
	cnts = rint ();
	for ( int i = 1; i <= cnts; ++ i ) s[i] = rint ();
	std::sort ( s + 1, s + cnts + 1, []( const int a, const int b ) { return dfn[a] < dfn[b]; } );
	int cnt = 0;
	for ( int i = 1; i < cnts; ++ i ) {
		int u = s[i], v = s[i + 1], t = calcLCA ( u, v );
		cnt += sum[u] + sum[v] - 2 * sum[t];
	}
	int u = s[1], v = s[cnts], t = calcLCA ( u, v );
	cnt += sum[u] + sum[v] - 2 * sum[t];
	cnt /= 2, cnt += ( t <= n ) - cnts;
	printf ( "%d\n", cnt );
}

int main () {
	for ( int T = rint (); T --; clear () ) {
		n = snode = rint (), m = rint ();
		for ( int i = 1, u, v; i <= m; ++ i ) {
			u = rint (), v = rint ();
			src.add ( u, v );
		}
		Tarjan ( 1, 0 ), dfc = 0;
		initDFN ( 1, 0 ), initST ();
		for ( q = rint (); q --; ) solve ();
	}
	return 0;
}
``