题目链接。
题意:
给一张有向图G,求一个结点数最大的结点集,使得该结点中任意两个结点 u 和 v满足:要么 u 可以到达 v, 要么 v 可以到达 u(u 和 v 相互可达也可以)。
分析:
”同一个强连通分量中的点要么都选,要么不选。把强连通分量收缩点后得到SCC图,让每个SCC结点的权等于它的结点数,则题目转化为求SCC图上权最大的路径。由于SCC图是一个 DAG, 可以用动态规划求解。“
注意:
假设含有 n 个点的图中不含结点数大于 1 的强连通分量,那么缩点后的图含有 n 个点,由于 scc_cnt 是从 1 开始编号,所以初始化时要用<=n 而非 <n。
for(int i=1; i<=n; i++) Map[i].clear();
AC代码如下:
#include <iostream> #include <vector> #include <stack> #include <cstring> #include <cstdio> #include <algorithm> #include <cstdlib> using namespace std; const int maxn = 1000+10; vector<int> G[maxn], Map[maxn]; stack<int> S; int pre[maxn], lowlink[maxn], scc_cnt, sccno[maxn], dfs_clock, val[maxn], dp[maxn]; void dfs(int u){ pre[u] = lowlink[u] = ++dfs_clock; S.push(u); for(int i=0; i<G[u].size(); i++){ int v = G[u][i]; if(!pre[v]){ dfs(v); lowlink[u] = min(lowlink[u], lowlink[v]); } else if(!sccno[v]){ lowlink[u] = min(lowlink[u], pre[v]); } } if(lowlink[u] == pre[u]){ scc_cnt++; for(;;){ int x = S.top(); S.pop(); sccno[x] = scc_cnt; if(x == u) break; } } } void find_scc(int n){ scc_cnt = dfs_clock = 0; memset(pre, 0, sizeof(pre)); memset(sccno, 0, sizeof(sccno)); for(int i=0; i<n; i++){ if(!pre[i]) dfs(i); } } int d(int x){ if(dp[x]) return dp[x]; else if(Map[x].size() == 0) return dp[x] = val[x]; int ans = 0; for(int i=0; i<Map[x].size(); i++){ ans = max(ans, d(Map[x][i])); } return dp[x] = ans+val[x]; } int main(){ int n, m, u, v, T; scanf("%d", &T); while(T--){ scanf("%d%d", &n, &m); memset(val, 0, sizeof(val)); memset(dp, 0, sizeof(dp)); for(int i=0; i<=n; i++) { G[i].clear(); Map[i].clear(); } //这里要用<= for(int i=0; i<m; i++) { scanf("%d%d", &u, &v); u--; v--; G[u].push_back(v); } find_scc(n); //缩点 for(int u=0; u<n; u++) { val[sccno[u]]++; for(int i=0; i<G[u].size(); i++){ int v = G[u][i]; if(sccno[v] != sccno[u]) Map[sccno[u]].push_back(sccno[v]); } } int ans = 0; for(int i=1; i<=scc_cnt; i++) ans = max(ans, d(i)); printf("%d\n", ans); } return 0; }