通过观察可得:
1.一个强连通分量为一个半连通子图
2.一条链为一个半连通子图
执行$tarjan$算法缩点后,得到的图是一个DAG,使每个点的权值为这个点所包含的点数,问题就转化成了求DAG上的最长链(链上的点权值和最大)的大小和个数。
既然是DAG,很明显可以想到DP。
$f[u]$为以点$u$结尾的链的最大长度,那么很明显,状态转移方程为$f[v] = max(f[v],f[u] + size[v])$。
$g[u]$为以点$u$结尾的最大链的个数,当$f[v] < f[u] + size[v]$时,$g[u] = f[u]$,当$f[v] = f[u] + size[v]$时,$g[u] = g[u] + 1$
这样去DP的话,是需要拓扑排序的,如果$tarjan$缩点后去处理每个点的入度明显会超时。
这里有一个技巧,$tarjan$是DFS,深度优先遍历整个图,只要从强连通分量的数量倒着枚举到一,就是缩点后形成的DAG的拓扑序。
另外注意DP时注意重边,要去重。
$Code:$
#include <iostream> #include <cstdio> #include <algorithm> #include <vector> #include <stack> #include <cstring> using namespace std; const int MAXN = 100000 + 10; int n,m,k,x; int low[MAXN],dfn[MAXN],color[MAXN],nodecnt,cnt,f[MAXN],siz[MAXN],ans,num[MAXN]; int vis[MAXN]; vector<int> e[MAXN]; vector<int> en[MAXN]; stack<int> s; void tarjan(int u) { dfn[u] = low[u] = ++cnt; s.push(u); int len = e[u].size(); for(int i = 0; i < len; i++) { int v = e[u][i]; if(!dfn[v]) { tarjan(v); low[u] = min(low[u],low[v]); } else if(!color[v]){ low[u] = min(low[u],dfn[v]); } } if(low[u] == dfn[u]) { ++nodecnt; int t; do{ siz[nodecnt]++; t = s.top(); s.pop(); color[t] = nodecnt; }while(t != u); } } void DP() { for(int i = nodecnt; i >= 1; i--) { int len = en[i].size(); for(int j = 0; j < len; j++) { int v = en[i][j]; if(vis[v] == i) continue; vis[v] = i; if(f[v] < f[i] + siz[v]) { f[v] = f[i] + siz[v]; num[v] = num[i]; } else if(f[v] == f[i] + siz[v]) { num[v] += num[i]; num[v] %= x; } } } } int main() { scanf("%d%d%d",&n,&m,&x); for(int i = 1; i <= m; i++) { int u,v; scanf("%d%d",&u,&v); e[u].push_back(v); } for(int i = 1; i <= n; i++) { if(!dfn[i]) tarjan(i); } for(int i = 1; i <= n; i++) { int len = e[i].size(); for(int j = 0; j < len; j++) { if(color[i] == color[e[i][j]]) continue; en[color[i]].push_back(color[e[i][j]]); } } for(int i = 1; i <= nodecnt; i++) { f[i] = siz[i]; num[i] = 1; } DP(); for(int i = 1; i <= nodecnt; i++) { if(f[i] > k) { k = f[i]; ans = num[i]; ans %= x; } else if(f[i] == k) { ans += num[i]; ans %= x; } } printf("%d %d ",k,ans); return 0; }