Solution -「CF 1361E」James and the Chase
阿新 • • 發佈:2020-09-23
\(\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\%\) 之類的奇怪限制就可以往這方面想啦。