• P3387 【模板】缩点 Tarjan强连通分量/树上dp


    P3387 【模板】缩点 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

    首先,什么是强连通分量

    极大强连通子图 叫做强连通分量

                首先要明确,

                        1. 极大强连通子图肯定是连通的,

                        2.若在增加一个结点,他们所构成的图将不是连通的

                        3.若该有向图是强连通图,那么其极大强连通子图就是他本身

                            (那么其强连通分量就为一,也就是说有几个极大强连通子图就有几个强连通分量)

                        4.非强连通图有多个极大强连通子图
    ————————————————
    版权声明:本文为CSDN博主「爱叨叨的小嘟」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/L_Z_jay/article/details/112289485

    所谓缩点,就是将有向有环图中的网或者环结构合并看作一个点,使原图称为DAG(有向无环图)以便对图进行操作,如求最长路最短路等等。

    所以我们的主要思路就是,求出这个有向有环图中的强连通分量,并将其合并成一个代表点,之后重新组成一个DAG,对这个DAG求最长路。

    而求强连通分量用到了Tarjan算法,之后再详细写一篇博客总结。其主要思路就是设置一个dfs顺序的数组dfn和一个表示该节点可以到达的dfn最小的节点的顺序数组low

    当dfn[i]==low[i]的时候,说明这个i点可以重新回到i,简单说可以看作构成了一个环或者网状结构。如果保证这个环或网已经是最大的了,那么也就找到了一个强连通分量。

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. #pragma warning(disable:4996);
    6. #define ll long long
    7. #define int ll
    8. #define rep(j,b,e) for(register int j=b;j<=e;j++)
    9. #define drep(j,e,b) for(register int j=(e);j>=(b);j--)
    10. int T = 1;
    11. const int N = 2e5 + 10;
    12. const int mod = 10000;
    13. int n, m, k, q;
    14. template <class T, class ...Arg>
    15. void inline pln(T t, Arg ...args);//打印一行参数
    16. template<typename T>
    17. void inline pln(vector a, string s = " ");
    18. template<class T, class ...Arg>
    19. void inline inln(T& t, Arg&...args);//输入一行数据
    20. //--------------------------------------
    21. struct edge {
    22. int from, to;
    23. }edg[N];
    24. int val[N];
    25. int dfn[N];//dfs顺序
    26. int low[N];//最早能回溯到的时间
    27. vector<int>gra[N];//原图
    28. vector<int>DAG[N];//之后构建的新图
    29. vector<int>stk, inStk(N);//存储节点
    30. vector<int>pre(N);//表示强连通分量的代表节点
    31. int Time = 0;//记录dfs顺序
    32. void Tarjan(int now) {
    33. dfn[now] = low[now] = ++Time;//“递”的时候初始化dfs顺序
    34. stk.push_back(now);
    35. inStk[now] = 1;
    36. for (auto& nx : gra[now]) {
    37. if (dfn[nx] == 0) {
    38. Tarjan(nx);
    39. low[now] = min(low[now], low[nx]);// 如果子节点可以回到更早的
    40. //地方那么now也可以回到。
    41. }
    42. else if (inStk[nx]) {//如果这个点还在栈中,则松弛一次
    43. low[now] = min(low[now], dfn[nx]);
    44. }
    45. }
    46. if (dfn[now] == low[now]) {//找到了一个强连通分量
    47. int x = -1;
    48. do {
    49. x = stk.back();
    50. stk.pop_back();
    51. inStk[x] = 0;
    52. pre[x] = now;//将强连通分量的元素值加到一个代表元素上
    53. //使环看作一个点,包括了独立的单个节点
    54. } while (x != now);
    55. }
    56. }
    57. vector<int>dp(N, 0);
    58. int dfs(int now) {//求缩点完的DAG的最长路,树上dp求每点的最长路
    59. if (dp[now])return dp[now];
    60. int res = 0;
    61. for (auto nx : DAG[now]) {
    62. res = max(res, dfs(nx));
    63. }
    64. return dp[now] = res + val[now];
    65. }
    66. void init(int n) {//并查集类似的初始化
    67. rep(j, 1, n)pre[j] = j;
    68. }
    69. signed main()
    70. {
    71. #ifndef ONLINE_JUDGE
    72. //freopen("out.txt", "w", stdout);
    73. #endif
    74. ios::sync_with_stdio(0); cout.tie(0);
    75. inln(n, m);
    76. init(n);
    77. rep(j, 1, n) {
    78. cin >> val[j];
    79. }
    80. rep(j, 1, m) {
    81. int f, t;
    82. cin >> f >> t;
    83. edg[j] = { f,t };
    84. gra[f].push_back(t);
    85. }
    86. rep(j, 1, n) {
    87. if (dfn[j] == 0)
    88. Tarjan(j);
    89. }
    90. rep(j, 1, n) {
    91. if (pre[j] != j) {//子节点j的值加到父节点,缩点
    92. val[pre[j]] += val[j];
    93. }
    94. }
    95. rep(j, 1, m) {
    96. auto [f, t] = edg[j];
    97. if (pre[f] == pre[t])//如果是一个环内的跳过
    98. continue;
    99. else {//因为代表点代替了强连通分量,新图建立全依靠代表节点
    100. DAG[pre[f]].push_back(pre[t]);
    101. }
    102. }
    103. int ans = 0;
    104. rep(j, 1, n) {
    105. ans = max(ans, dfs(j));
    106. }
    107. pln(ans);
    108. return 0;
    109. }
    110. //--------------------------------------
    111. void inline inln() {}
    112. template<class T, class ...Arg>
    113. void inline inln(T& t, Arg&...args) {
    114. cin >> t;
    115. inln(args...);
    116. }
    117. void inline pln() {
    118. cout << "\n";
    119. }
    120. template <class T, class ...Arg>
    121. void inline pln(T t, Arg ...args) {
    122. cout << t << " ";
    123. pln(args...);
    124. }
    125. template<typename T>
    126. void inline pln(vector a, string s) {
    127. for (T& x : a) {
    128. cout << x << s;
    129. }
    130. cout << endl;
    131. }

    一.缩点应用

     如果是DAG,对整个图来一个动态规划即可。也就是对每个点记忆化搜索。取最值。但是此题图中可能存在环,所以采用缩点的思路将环合并为一个点。从题目来看,这个环应该合并为这个环内编号最大的点。合并完后重新建图即可。

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. #pragma warning(disable:4996);
    6. #define ll long long
    7. #define int ll
    8. #define rep(j,b,e) for(register int j=b;j<=e;j++)
    9. #define drep(j,e,b) for(register int j=(e);j>=(b);j--)
    10. int T = 1;
    11. const int N = 2e5 + 10;
    12. const int mod = 10000;
    13. int n, m, k, q;
    14. template <class T, class ...Arg>
    15. void inline pln(T t, Arg ...args);//打印一行参数
    16. template<typename T>
    17. void inline pln(vector a, string s = " ");
    18. template<class T, class ...Arg>
    19. void inline inln(T& t, Arg&...args);//输入一行数据
    20. //-------------------------------------
    21. struct edge {
    22. int f, t;
    23. }edg[N];
    24. int dfn[N] = { 0 };
    25. int low[N] = { 0 };
    26. vector<int>gra[N];
    27. vector<int>DAG[N];
    28. vector<int>stk;
    29. vector<int>inStk;
    30. vector<int>pre;//存储祖先
    31. vector<int>Max;//保存缩点完每个点的编号最大值,初始化为自身
    32. vector<int>vis;
    33. vector<int>ans;
    34. int Time = 0;
    35. void tarjan(int now) {
    36. dfn[now] = low[now] = ++Time;
    37. stk.push_back(now);
    38. inStk[now] = 1;
    39. for (auto nx : gra[now]) {
    40. if (dfn[nx] == 0) {
    41. tarjan(nx);
    42. low[now] = min(low[now], low[nx]);
    43. }
    44. else if (inStk[nx]) {
    45. low[now] = min(low[now], dfn[nx]);
    46. }
    47. }
    48. if (dfn[now] == low[now]) {
    49. int x = -1;
    50. int mx = 0;
    51. do {
    52. x = stk.back();
    53. stk.pop_back();
    54. inStk[x] = 0;
    55. mx = max(x, mx);
    56. pre[x] = now;
    57. } while (x != now);
    58. Max[now] = max(Max[now], mx);//保存环中的最大值
    59. }
    60. }
    61. void newDAG() {//新图
    62. rep(j, 1, n) {
    63. if (dfn[j] == 0)
    64. tarjan(j);
    65. }
    66. rep(j, 1, m) {
    67. auto [f, t] = edg[j];
    68. if (pre[f] != pre[t]) {
    69. DAG[pre[f]].push_back(pre[t]);
    70. }
    71. }
    72. }
    73. int dfs(int now) {
    74. if (ans[now])return ans[now];
    75. int res = Max[now];
    76. for (auto nx : DAG[now]) {
    77. res = max(res, dfs(nx));
    78. }
    79. return ans[now] = res;
    80. }
    81. signed main()
    82. {
    83. #ifndef ONLINE_JUDGE
    84. //freopen("in.txt", "r", stdin);
    85. //freopen("out.txt", "w", stdout);
    86. #endif
    87. ios::sync_with_stdio(0); cout.tie(0);
    88. Max.assign(N, 0);
    89. vis.assign(N, 0);
    90. ans.assign(N, 0);
    91. pre.assign(N, 0);
    92. inStk.assign(N, 0);
    93. inln(n, m);
    94. rep(j, 1, m) {
    95. int f, t;
    96. inln(f, t);
    97. edg[j] = { f,t };
    98. gra[f].push_back(t);
    99. }
    100. rep(j, 1, n)Max[j] = j;
    101. rep(j, 1, n)pre[j] = j;
    102. newDAG();
    103. rep(j, 1, n) {
    104. if (ans[j] == 0)
    105. dfs(j);
    106. }
    107. rep(j, 1, n) {
    108. cout << ans[pre[j]] << " ";
    109. }
    110. return 0;
    111. }
    112. //--------------------------------------
    113. void inline inln() {}
    114. template<class T, class ...Arg>
    115. void inline inln(T& t, Arg&...args) {
    116. cin >> t;
    117. inln(args...);
    118. }
    119. void inline pln() {
    120. cout << "\n";
    121. }
    122. template <class T, class ...Arg>
    123. void inline pln(T t, Arg ...args) {
    124. cout << t << " ";
    125. pln(args...);
    126. }
    127. template<typename T>
    128. void inline pln(vector a, string s) {
    129. for (T& x : a) {
    130. cout << x << s;
    131. }
    132. cout << endl;
    133. }

  • 相关阅读:
    Java XX市软件产业人才公共服务平台
    使用树莓派学习PostgreSQL
    基于ssm的贫困生管理系统javaEE
    1.3.16 标准 IP 访问控制列表配置
    openai/CLIP 代码样例报告
    【Linux】之Centos7安装KVM虚拟化
    【Web漏洞探索】命令注入漏洞
    TypeScript(一) —— 进阶(TypeScript 中的类型、编译选项及使用 webpack 打包 ts 代码)
    【权限提升-Windows提权】-UAC提权之MSF模块和UACME项目-DLL劫持-不带引号服务路径-不安全的服务权限
    Rancher安装并部署Kubernetes高可用集群
  • 原文地址:https://blog.csdn.net/m0_60777643/article/details/126020263