题解 ==============================================================
一、内容
一个有向图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 <unordered_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];
unordered_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 + id[v];
if (!mp.count(hash)) {
mp[hash]++; add(sh, id[u], id[v]);
}
}
}
}
//通过拓扑图进行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;
}
我想在建新图(缩完点的图)之前是应该把len=0归零的吧(新图的边序号应该从新从1开始),但是中间加了这句就错了,是哪里理解错了吗
老哥你这。。wa啊
复制错了。