Address
Description
- 一个有向图
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≤100000,M≤1000000,X≤108
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
Solution
- 对问题进行转换。
- 如果原图的某一部分是一个强连通分量,那么这一部分显然是半连通子图。
- 如果原图的某一部分是一条链,那么这一部分显然也是半连通子图。
- 因此我们先用Tarjan缩点,新图是一个有向无环图,然后问题就变为求新图的最长链长度及个数。
- 显然要用拓扑序来做DP,设
f[x]
表示到第
x
个强连通分量的最长链个数,
g[x] 表示长度,记 num[x] 第 x 个强连通分量的点数。 - 则:
- 若
g[x]+num[y]=g[y],f[y]+=f[x] - 若 g[x]+num[y]>g[y],f[y]=f[x],g[y]=g[x]+num[y]
- 注意缩点以后不能建多余的边,因此在拓扑排序前要将所有边排序后去重。
- 本题的瓶颈在于边的排序,时间复杂度 O(mlogm) ,期望得分100分。
Code
#include <iostream> #include <cstdio> #include <cctype> #include <algorithm> #include <cstring> using namespace std; namespace INOUT { const int S = 1 << 20; char frd[S], *hed = frd + S; const char *tal = hed; inline char nxtChar() { if (hed == tal) fread(frd, 1, S, stdin), hed = frd; return *hed++; } inline int get() { char ch; int res = 0; bool flag = false; while (!isdigit(ch = nxtChar()) && ch != '-'); (ch == '-' ? flag = true : res = ch ^ 48); while (isdigit(ch = nxtChar())) res = res * 10 + ch - 48; return flag ? -res : res; } inline void put(int x) { if (x > 9) put(x / 10); putchar(x % 10 + 48); } }; using namespace INOUT; const int N = 1e5 + 5, M = 1e6 + 5; int n, m, Xmod, C, top, tis, E; int dfn[N], low[N], stk[N], col[N], num[N]; int rin[N], rout[N], f[N], g[N]; bool inv[N]; struct Link { int l, r; friend inline bool operator < (const Link &x, const Link &y) { return x.l < y.l || x.l == y.l && x.r < y.r; } }a[M]; struct Edge { int to; Edge *nxt; }; Edge p[M], *T = p, *lst[N]; Edge q[M], *Q = q, *rst[N]; inline void RinkEdge(int x, int y) { (++Q)->nxt = rst[x]; rst[x] = Q; Q->to = y; ++rin[y]; ++rout[x]; } inline void LinkEdge(int x, int y) { (++T)->nxt = lst[x]; lst[x] = T; T->to = y; } inline void CkMin(int &x, int y) {if (x > y) x = y;} inline void Swap(int &x, int &y) {int t = x; x = y; y = t;} inline void Tarjan(int x) { inv[x] = true; stk[++top] = x; dfn[x] = low[x] = ++tis; int y; for (Edge *e = lst[x]; e; e = e->nxt) if (!dfn[y = e->to]) Tarjan(y), CkMin(low[x], low[y]); else if (inv[y]) CkMin(low[x], dfn[y]); if (dfn[x] == low[x]) { inv[x] = false; num[col[x] = ++C] = 1; while (y = stk[top--], y != x) inv[y] = false, ++num[col[y] = C]; } } int main() { //freopen("semi.in", "r", stdin); //freopen("semi.out", "w", stdout); n = get(); m = get(); Xmod = get(); int x, y; for (int i = 1; i <= m; ++i) { x = get(); y = get(); LinkEdge(x, y); } for (int i = 1; i <= n; ++i) if (!dfn[i]) Tarjan(i); for (int i = 1; i <= n; ++i) for (Edge *e = lst[i]; e; e = e->nxt) if (col[i] != col[y = e->to]) a[++E].l = col[i], a[E].r = col[y]; sort(a + 1, a + E + 1); RinkEdge(a[1].l, a[1].r); for (int i = 2; i <= E; ++i) if (a[i].l == a[i - 1].l && a[i].r == a[i - 1].r) continue; else RinkEdge(a[i].l, a[i].r); ++C; for (int i = 1; i < C; ++i) if (!rout[i]) RinkEdge(i, C); for (int i = 1; i <= C; ++i) if (!rin[i]) stk[++top] = i; for (int i = 1; i <= C; ++i) f[i] = 1, g[i] = num[i]; while (top) { x = stk[top--]; for (Edge *e = rst[x]; e; e = e->nxt) { y = e->to; if (g[x] + num[y] > g[y]) g[y] = g[x] + num[y], f[y] = 0; if (g[x] + num[y] == g[y]) (f[y] += f[x]) %= Xmod; if (!--rin[y]) stk[++top] = y; } } printf("%d\n%d", g[C], f[C]); return 0; }
- 若