1. 程式人生 > 實用技巧 >Solution -「CF 1361E」James and the Chase

Solution -「CF 1361E」James and the Chase

\(\mathcal{Description}\)

  Link.

  給定 \(n\) 個點 \(m\) 條邊的有向弱連通圖。稱一個點是“好點”當且僅當從該點出發,不存在到同一點的兩條不同簡單路徑。求出所有好點,但若好點個數少於 \(n \times 20\%\),僅輸出 -1

  多測,\(n,\sum_{}^{} n \le10^5\)\(m,\sum_{}^{} m\le2\times10^5\)

\(\mathcal{Solution}\)

  先來想一想如何判斷某個點 \(r\) 是好點。以 \(r\) 為根建出任意一棵外向生成樹,不難發現 \(r\) 是好點,當且僅當樹外不存在橫叉邊(有向 DFS 樹是可能存在橫叉邊的)。

  假設我們已經得到了一點 \(r\) 為好點,如何判斷出其他點是否是好點呢?考慮在剛才那棵外向生成樹上的某一非根結點 \(u\),若從 \(u\) 子樹內向 \(u\) 的祖先的返祖邊多於一條,就顯然不合法,否則若 \(u\) 能到達最高的祖先合法,\(u\) 也必然合法。

  最後一個問題,怎麼找到一個 \(r\) 呢?考慮到 \(20\%\) 的限制,隨機 \(100\) 個點進行好點測驗,如果都不是好點直接輸出 -1。錯誤率為 \(\left( \frac{4}{5} \right)^{100}\),非常可觀。

  複雜度 \(\mathcal O(100\sum_{}^{} n)\)

\(\mathcal{Code}\)

/* Clearink */

#include <cstdio>
#include <random>

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

template<typename Tp>
inline void wint ( Tp x ) {
	if ( x < 0 ) putchar ( '-' ), x = ~ x + 1;
	if ( 9 < x ) wint ( x / 10 );
	putchar ( x % 10 ^ '0' );
}

const int MAXN = 1e5, MAXM = 2e5;
int n, m, ecnt, head[MAXN + 5], vtag[MAXN + 5], upc[MAXN + 5], top[MAXN + 5];
bool bad[MAXN + 5];
std::mt19937 rnd ( 20050913 );

struct Edge { int to, nxt; } graph[MAXM + 5];

inline void link ( const int s, const int t ) {
	graph[++ ecnt] = { t, head[s] };
	head[s] = ecnt;
}

inline bool check ( const int u ) {
	vtag[u] = 1;
	for ( int i = head[u], v; i; i = graph[i].nxt ) {
		if ( !vtag[v = graph[i].to] ) {
			if ( !check ( v ) ) return false;
		} else if ( vtag[v] == 2 ) {
			return false;
		}
	}
	vtag[u] = 2;
	return true;
}

inline void mark ( const int u ) {
	top[u] = u;
	for ( int i = head[u], v; i; i = graph[i].nxt ) {
		if ( !vtag[v = graph[i].to] ) {
			vtag[v] = vtag[u] + 1;
			mark ( v ), upc[u] += upc[v];
			if ( vtag[top[v]] < vtag[top[u]] ) top[u] = top[v];
		} else {
			++ upc[u], -- upc[v];
			if ( vtag[v] < vtag[top[u]] ) top[u] = v;
		}
	}
}

inline int spread ( const int u ) {
	vtag[u] = 1, bad[u] = upc[u] > 1;
	int ret = !( bad[u] |= bad[top[u]] );
	for ( int i = head[u], v; i; i = graph[i].nxt ) {
		if ( !vtag[v = graph[i].to] ) {
			ret += spread ( v );
		}
	}
	return ret;
}

inline void clear () {
	ecnt = 0;
	for ( int i = 1; i <= n; ++ i ) {
		head[i] = vtag[i] = upc[i] = top[i] = bad[i] = 0;
	}
}

int main () {
	for ( int T = rint (); T --; ) {
		clear ();
		n = rint (), m = rint ();
		for ( int i = 1, u, v; i <= m; ++ i ) {
			u = rint (), v = rint ();
			link ( u, v );
		}
		int rt = 0;
		for ( int i = 1; i <= 100 && !rt; ++ i ) {
			int u = rnd () % n + 1;
			for ( int i = 1; i <= n; ++ i ) vtag[i] = 0;
			if ( check ( u ) ) rt = u;
		}
		if ( !rt ) { puts ( "-1" ); continue; }
		for ( int i = 1; i <= n; ++ i ) vtag[i] = 0;
		vtag[rt] = 1, mark ( rt );
		for ( int i = 1; i <= n; ++ i ) vtag[i] = 0;
		int cnt = spread ( rt );
		if ( cnt * 5 < n ) { puts ( "-1" ); continue; }
		for ( int i = 1, f = 0; i <= n; ++ i ) {
			if ( !bad[i] ) {
				if ( f ) putchar ( ' ' );
				f = 1, printf ( "%d", i );
			}
		}
		putchar ( '\n' );
	}
	return 0;
}

\(\mathcal{Details}\)

  這種隨機亂搞題得放開點腦洞啊,看到這種 \(20\%\) 之類的奇怪限制就可以往這方面想啦。