[bzoj3887][Usaco2015 Jan]Grass Cownoisseur_trajan_拓扑排序_拓扑序dp
作者:互联网
[Usaco2015 Jan]Grass Cownoisseur
题目大意:给一个有向图,然后选一条路径起点终点都为1的路径出来,有一次机会可以沿某条边逆方向走,问最多有多少个点可以被经过?(一个点在路径中无论出现多少正整数次对答案的贡献均为1)
数据范围:$1\le n, m\le 10^5$。
题解:
先$tarjan$缩强连通分量,因为每一个$SCC$只要能到一个点就能到整个$SCC$。
接下来我们发现,我们操作的边的两个端点会满足如下性质:
这条有向边的起点可以到$1$号点所在$SCC$。
这条有向边的重点可以被$1$号点所在$SCC$到达。
故此,我们再缩完点之后,先对原图弄一遍拓扑序$DP$,求出$1$号点所在$SCC$到每个点的最长路。
再建反边重新跑拓扑序$DP$,求出每个点到$1$号点所在$SCC$的最长路。
暴力枚举边更新即可。
代码:
#include <bits/stdc++.h> #define N 100010 using namespace std; int dep[N], low[N], st[N], top, cnt, blg[N], sz[N], f1[N], f2[N], d1[N], d2[N]; int Number; bool ins[N]; struct Node { int x, y; }e[N]; struct Edge { int head[N], to[N << 1], nxt[N << 1], tot; inline void add(int x, int y) { to[ ++ tot] = y; nxt[tot] = head[x]; head[x] = tot; } }G1,G2,G3; char *p1, *p2, buf[100000]; #define nc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1 ++ ) int rd() { int x = 0, f = 1; char c = nc(); while (c < 48) { if (c == '-') f = -1; c = nc(); } while (c > 47) { x = (((x << 2) + x) << 1) + (c ^ 48), c = nc(); } return x * f; } void tarjan(int p) { st[ ++ top] = p; ins[p] = true; low[p] = dep[p] = ++cnt; for (int i = G1.head[p]; i; i = G1.nxt[i]) { if (!dep[G1.to[i]]) tarjan(G1.to[i]), low[p] = min(low[p], low[G1.to[i]]); else if (ins[G1.to[i]]) low[p] = min(low[p], dep[G1.to[i]]); } if (dep[p] == low[p]) { int t; Number ++ ; do { t = st[top -- ]; ins[t] = false; blg[t] = Number; sz[Number] ++ ; } while(t != p); } } queue<int> q; void dp1() { while (!q.empty()) { q.pop(); } memset(f1, 0xef, sizeof f1); for (int i = 1; i <= Number; i ++ ) { if (!d1[i]) { q.push(i); } } f1[blg[1]] = 0; while (!q.empty()) { int x = q.front(); q.pop(); f1[x] += sz[x]; for (int i = G2.head[x]; i; i = G2.nxt[i]) { f1[G2.to[i]] = max(f1[G2.to[i]], f1[x]); d1[G2.to[i]] -- ; if (!d1[G2.to[i]]) { q.push(G2.to[i]); } } } } void dp2() { while (!q.empty()) { q.pop(); } for (int i = 1; i <= Number; i ++ ) { if (!d2[i]) { q.push(i); } } memset(f2, 0xef, sizeof f2); f2[blg[1]] = 0; while (!q.empty()) { int x = q.front(); q.pop(); f2[x] += sz[x]; for (int i = G3.head[x]; i; i = G3.nxt[i]) { f2[G3.to[i]] = max(f2[G3.to[i]], f2[x]); d2[G3.to[i]] -- ; if (!d2[G3.to[i]]) { q.push(G3.to[i]); } } } } int main() { int n = rd(), m = rd(); if (!n) puts("1"), exit(0); for (int i = 1; i <= m; i ++ ) { e[i].x = rd(), e[i].y = rd(); G1.add(e[i].x, e[i].y); } for (int i = 1; i <= n; i ++ ) { if (!dep[i]) { tarjan(i); } } // for (int i = 1; i <= n; i ++ ) { // printf("%d ",blg[i]); // } // puts(""); for (int i = 1; i <= m; i ++ ) { e[i].x = blg[e[i].x]; e[i].y = blg[e[i].y]; if (e[i].x != e[i].y) { // printf("%d %d\n", e[i].x, e[i].y); G2.add(e[i].x, e[i].y); d1[e[i].y] ++ ; G3.add(e[i].y, e[i].x); d2[e[i].x] ++ ; } } dp1(); dp2(); // for (int i = 1; i <= Number; i ++ ) { // printf("%d %d\n", f1[i], f2[i]); // } int ans = sz[blg[1]]; for (int i = 1; i <= m; i ++ ) { if (e[i].x != e[i].y) { ans = max(ans, f1[e[i].y] + f2[e[i].x] - sz[blg[1]]); } } cout << ans << endl ; return 0; }
小结:比较好想的一道题,需要注意的是两边拓扑序$dp$需要清队列。
标签:f1,Usaco2015,trajan,拓扑,SCC,int,所在,号点 来源: https://www.cnblogs.com/ShuraK/p/11255775.html