[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$需要清队列。

posted @ 2019-07-27 16:56  JZYshuraK_彧  阅读(186)  评论(0编辑  收藏  举报