BZOJ 1093 最大半连通子图 SCC + DP

一、内容

 一个有向图G=(V,E)称为半连通的(Semi-Connected),如果满足:?u,v∈V,满足u→v或v→u,即对于图中任意
两点u,v,存在一条u到v的有向路径或者从v到u的有向路径。若G'=(V',E')满足V'?V,E'是E中所有跟V'有关的边,
则称G'是G的一个导出子图。若G'是G的导出子图,且G'半连通,则称G'为G的半连通子图。若G'是G所有半连通子图
中包含节点数最多的,则称G'是G的最大半连通子图。给定一个有向图G,请求出G的最大半连通子图拥有的节点数K
,以及不同的最大半连通子图的数目C。由于C可能比较大,仅要求输出C对X的余数。

Input

  第一行包含两个整数N,M,X。N,M分别表示图G的点数与边数,X的意义如上文所述接下来M行,每行两个正整
数a, b,表示一条有向边(a, b)。图中的每个点将编号为1,2,3…N,保证输入中同一个(a,b)不会出现两次。N ≤1
00000, M ≤1000000;对于100%的数据, X ≤10^8

Output

  应包含两行,第一行包含一个整数K。第二行包含整数C Mod X.

Sample Input

6 6 20070603
1 2
2 1
1 3
2 4
5 6
6 4

Sample Output

3
3

二、思路

  • 首先强连通分量必定是半连通子图。当我们建立出强连通图的拓扑图的时候,从入度为0的点出发,能走的所有最长的路径便是最大半连通子图。
  • dp[i]: 代表拓扑序从起点 到 i这个点最大的节点数。 cnt[i]:代表从起点到i的最大方案数。
  • 直接在拓扑图上dp一下即可。
  • 注意缩点后,每个点可能和其他点有多条边,这个时候我们应该去掉重边。 因为最大半连通子图包括的是所有边,不同的连通子图只会因为点不相同而不同。

三、代码

#include <cstdio>
#include <algorithm>
#include <map>
#include <iostream>
typedef long long ll;
using namespace std;
const int N = 1e5 + 5, M = 2e6 + 5; 
struct E{ int v, next;} e[M];
int n, m, mod, num, u, v, top, len, h[N], sh[N], scc_cnt, id[N], scc[N], low[N], stack[N], dfn[N], dp[N], cnt[N];
bool in_st[N];
map<ll, int> mp;
void add(int h[], int u, int v) {
	e[++len].v = v; e[len].next = h[u]; h[u] = len;
} 
void tarjan(int u) {
	dfn[u] = low[u] = ++num;
	stack[++top] = u; in_st[u] = true;
	for (int j = h[u]; j; j = e[j].next)  {
		int v = e[j].v;
		if (!dfn[v]) {
			tarjan(v);
			low[u] = min(low[u], low[v]);
		} else if (in_st[v]) low[u] = min(low[u], dfn[v]);
	}
	if (dfn[u] == low[u]) {
		++scc_cnt; int v;
		do {
			v = stack[top--]; in_st[v] = false;
			id[v] = scc_cnt; scc[scc_cnt]++; //连通分量的节点数++ 
		} while (u != v);
	}
}
int main() {
	scanf("%d%d%d", &n, &m, &mod);
	for (int i = 1; i <= m; i++) {
		scanf("%d%d", &u, &v); add(h, u, v);
	}
	for (int u = 1; u <= n; u++) if (!dfn[u]) tarjan(u);
	//建立SCC拓扑图
	for (int u = 1; u <= n; u++) {
		for (int j = h[u]; j; j = e[j].next) {
			int v = e[j].v;
			if (id[u] != id[v]) {
				ll hash = id[u] * N + v;
				if (!mp.count(hash)) {
					mp[hash]++; add(sh, id[u], id[v]);
				}
			}
		}
	} 
	cout << scc_cnt << endl; 
	//通过拓扑图进行dp求最大节点数 dp[]保存这条链的节点总数 cnt保存方案数 
	for (int u = scc_cnt; u >= 1; u--) {
		if (!dp[u]) dp[u] = scc[u], cnt[u] = 1; //代表它是入度为0的起点 
		for (int j = sh[u]; j; j = e[j].next) {
			int v = e[j].v;
			if (dp[v] < dp[u] + scc[v]) {
				dp[v] = dp[u] + scc[v];
				cnt[v] = cnt[u];
			} else if (dp[v] == dp[u] + scc[v]) cnt[v] = (cnt[v] + cnt[u]) % mod; 
		}
	} 
	//最后求一下最大值 
	int maxv = -1, c = 0;
	for (int u = scc_cnt; u >= 1; u--) {
		if (maxv < dp[u]) {
			maxv = dp[u];
			c = cnt[u];			
		} else if (maxv == dp[u]) c = (c + cnt[u]) % mod;
	}
	printf("%d\n%d", maxv, c);
	return 0; 
} 
发布了414 篇原创文章 · 获赞 380 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_41280600/article/details/104287604