[学习笔记] 割点 & 割边 & 双连通分量

一、定义

无向连通图 \(G = (V, E)\) 中,若存在一个点 \(u(u \in V)\) 使得删掉点 \(u\) 及其相连的边,会使原图不连通,就称 \(u\) 是原图的一个 割点 (cut vertex);若存在一条边 \((u, v)((u, v) \in E)\) 满足删掉 \((u, v)\) 后会使原图不连通,就称 \((u, v)\) 是原图的一座 桥(或者割边)(bridge)

若无向连通图 \(G = (V, E)\) 不存在割点,则称 \(G\)点双连通 (biconnected) 的。

若无向连通图 \(G = (V, E)\) 不存在桥,则称 \(G\)边双连通 (\(2\)-edge-connected) 的。

点双连通分量 (biconnected component) 是指极大点双连通子图。

边双连通分量 (\(2\)-edge-connected component) 是指极大边双连通子图。

二、Tarjan 求割边

很简单,如果 DFS 树的一个子树满足其中的返祖边不足以跳出这个子树,那么这个子树的根与其父亲之间的边就是割边。

image

image

下面给出此题代码:

/**
  * author : OMG_78
  * created: 2023-07-12-10.45.42
 **/
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Memory_Begins;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
inline bool ok (char c) {
  if (c < '0') return true;
  if (c > '9') return true;
  return false;
}
template < class Z >
inline void read (Z &tmp) {
  Z x = 0, f = 0;
  char c = getchar ();
  for ( ; ok (c) ; c = getchar ()) f = (c == '-') ? 1 : f;
  for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
  tmp = !f ? x : -x;
}
const int N = 2e5 + 5;
vector < pair < int, int > > G[N];
int ed[N][2];
int dfn[N], low[N], ck, n, m;
vector < int > bri;
inline void tarjan (int u, int eid) {
  dfn[u] = low[u] = ++ ck;
  for (int i = 0;i < G[u].size (); ++ i) {
    int v = G[u][i].first, eid2 = G[u][i].second;
    if (!dfn[v]) tarjan (v, eid2);
    if (eid != eid2) low[u] = min (low[u], low[v]);
  }
  if (dfn[u] == low[u] && eid != -1) bri.push_back (eid);
}
bool Memory_Ends;
signed main () {
  fprintf (stderr, "%.3lf MB\n", (&Memory_Begins - &Memory_Ends) / 1048576.0);
  read (n), read (m);
  for (int i = 1;i <= m; ++ i) {
    read (ed[i][0]), read (ed[i][1]);
    G[ed[i][0]].push_back (make_pair (ed[i][1], i));
    G[ed[i][1]].push_back (make_pair (ed[i][0], i));
  }
  for (int i = 1;i <= n; ++ i) {
    if (!dfn[i]) tarjan (i, -1);
  }
  printf ("%d\n", bri.size ());
  sort (bri.begin (), bri.end ());
  for (int i = 0;i < bri.size (); ++ i) {
    printf ("%d %d\n", ed[bri[i]][0], ed[bri[i]][1]);
  }
  fprintf (stderr, "%.3lf ms\n", 1e3 * clock () / CLOCKS_PER_SEC);
  return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/

三、Tarjan 求割点

思路及其类似,如果 \(u\) 子树中有一点 \(v\) 到不了非 \(u\) 的子树,那么 \(u\) 就是一个割点。

/**
  * author : OMG_78
  * created: 2023-07-12-11.19.33
 **/
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Memory_Begins;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
inline bool ok (char c) {
  if (c < '0') return true;
  if (c > '9') return true;
  return false;
}
template < class Z >
inline void read (Z &tmp) {
  Z x = 0, f = 0;
  char c = getchar ();
  for ( ; ok (c) ; c = getchar ()) f = (c == '-') ? 1 : f;
  for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
  tmp = !f ? x : -x;
}
const int N = 1e5 + 5;
int dfn[N], low[N], vis[N], cut[N];
int res, ck, n, m;
vector < int > G[N];
inline void tarjan (int u, int fa) {
  vis[u] = 1;
  dfn[u] = low[u] = ++ ck;
  int son = 0;
  for (int v : G[u]) {
    if (!vis[v]) {
      son ++;
      tarjan (v, u);
      low[u] = min (low[u], low[v]);
      if (fa != u && low[v] >= dfn[u] && !cut[u]) {
        cut[u] = 1; res ++;
      }
    }
    else if (v != fa) {
      low[u] = min (low[u], dfn[v]);
    }
  }
  if (fa == u && son > 1 && !cut[u]) {
    cut[u] = 1; res ++;
  }
}
bool Memory_Ends;
signed main () {
  fprintf (stderr, "%.3lf MB\n", (&Memory_Begins - &Memory_Ends) / 1048576.0);
  read (n), read (m);
  for (int i = 1;i <= m; ++ i) {
    int u, v;
    read (u), read (v);
    G[u].push_back (v);
    G[v].push_back (u);
  }
  for (int i = 1;i <= n; ++ i) {
    if (!vis[i]) {
      ck = 0;
      tarjan (i, i);
    }
  }
  printf ("%d\n", res);
  for (int i = 1;i <= n; ++ i) {
    if (cut[i]) printf ("%d ", i);
  }
  printf ("\n");
  fprintf (stderr, "%.3lf ms\n", 1e3 * clock () / CLOCKS_PER_SEC);
  return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/

四、边双连通分量

边双连通具有传递性。

性质:一张无向图缩完边双之后是 一棵树

/**
  * author : OMG_78
  * created: 2023-07-13-10.49.45
 **/
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Memory_Begins;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
inline bool ok (char c) {
  if (c < '0') return true;
  if (c > '9') return true;
  return false;
}
template < class Z >
inline void read (Z &tmp) {
  Z x = 0, f = 0;
  char c = getchar ();
  for ( ; ok (c) ; c = getchar ()) f = (c == '-') ? 1 : f;
  for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
  tmp = !f ? x : -x;
}
const int N = 5e5 + 5, M = 2e6 + 5;
int n, m, eid = 1, ans, ck, dfn[N], low[N], head[N], bel[N];
struct Edge {int to, nxt;} ed[M << 1];
bool vis[M << 1];
typedef vector < int > vint;
vector < vint > st;
inline void add_edge (int u, int v) {
  eid ++;
  ed[eid].to = v;
  ed[eid].nxt = head[u];
  head[u] = eid;
}
inline void tarjan (int u, int id) {
  dfn[u] = low[u] = ++ ck;
  for (int i = head[u]; i ; i = ed[i].nxt) {
    int v = ed[i].to;
    if (!dfn[v]) {
      tarjan (v, i);
      if (dfn[u] < low[v]) vis[i] = vis[i ^ 1] = 1;
      low[u] = min (low[u], low[v]);
    }
    else if (i != (id ^ 1)) low[u] = min (low[u], dfn[v]);
  }
}
inline void dfs (int u, int blo) {
  bel[u] = blo;
  st[blo].push_back (u);
  for (int i = head[u]; i ; i = ed[i].nxt) {
    int v = ed[i].to;
    if (bel[v] || vis[i]) continue;
    dfs (v, blo);
  }
}
bool Memory_Ends;
signed main () {
  fprintf (stderr, "%.3lf MB\n", (&Memory_Begins - &Memory_Ends) / 1048576.0);
  read (n), read (m);
  for (int i = 1;i <= m; ++ i) {
    int u, v;
    read (u), read (v);
    add_edge (u, v);
    add_edge (v, u);
  }
  for (int i = 1;i <= n; ++ i) {
    if (!dfn[i]) tarjan (i, 0);
  }
  st.push_back (vint ());
  for (int i = 1;i <= n; ++ i) {
    if (!bel[i]) st.push_back (vint ()), ans ++, dfs (i, ans);
  }
  printf ("%d\n", ans);
  for (int i = 1;i <= ans; ++ i) {
    sort (st[i].begin (), st[i].end ());
    printf ("%d ", st[i].size ());
    for (int j = 0;j < st[i].size (); ++ j) printf ("%d ", st[i][j]);
    printf ("\n");
  }
  fprintf (stderr, "%.3lf ms\n", 1e3 * clock () / CLOCKS_PER_SEC);
  return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/

五、点双连通分量

点双连通不具有传递性。

/**
  * author : OMG_78
  * created: 2023-07-13-10.36.27
 **/
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Memory_Begins;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
inline bool ok (char c) {
  if (c < '0') return true;
  if (c > '9') return true;
  return false;
}
template < class Z >
inline void read (Z &tmp) {
  Z x = 0, f = 0;
  char c = getchar ();
  for ( ; ok (c) ; c = getchar ()) f = (c == '-') ? 1 : f;
  for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
  tmp = !f ? x : -x;
}
const int N = 5e5 + 5, M = 2e6 + 5;
int stk[M << 1], tp, cnt, low[N], dfn[N], ck, n, m;
vector < int > st[N], G[N];
inline void tarjan (int u, int fa) {
  int son = 0;
  low[u] = dfn[u] = ++ ck;
  stk[++ tp] = u;
  for (int v : G[u]) {
    if (!dfn[v]) {
      son ++;
      tarjan (v, u);
      low[u] = min (low[u], low[v]);
      if (low[v] >= dfn[u]) {
        cnt ++;
        while (stk[tp + 1] != v) st[cnt].push_back (stk[tp --]);
        st[cnt].push_back (u);
      }
    }
    else if (v != fa) low[u] = min (low[u], dfn[v]);
  }
  if (!fa && !son) st[++ cnt].push_back (u);
}
bool Memory_Ends;
signed main () {
  fprintf (stderr, "%.3lf MB\n", (&Memory_Begins - &Memory_Ends) / 1048576.0);
  read (n), read (m);
  for (int i = 1;i <= m; ++ i) {
    int u, v;
    read (u), read (v);
    G[u].push_back (v);
    G[v].push_back (u);
  }
  for (int i = 1;i <= n; ++ i) {
    if (!dfn[i]) {
      tp = 0;
      tarjan (i, 0);
    }
  }
  printf ("%d\n", cnt);
  for (int i = 1;i <= cnt; ++ i) {
    sort (st[i].begin (), st[i].end ());
    printf ("%d ", st[i].size ());
    for (int j = 0;j < st[i].size (); ++ j) printf ("%d ", st[i][j]);
    printf ("\n");
  }
  fprintf (stderr, "%.3lf ms\n", 1e3 * clock () / CLOCKS_PER_SEC);
  return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/

六、洛谷 P2860 [USACO06JAN] Redundant Paths G

https://www.luogu.com.cn/problem/P2860

简化题意:给定一张无向连通图,求最少要加多少条边才能使得原图没有割边。

首先我们要缩边双,然后发现这是一棵树,容易发现只要把叶子节点都两两连起来,就没有割边了(因为形成了环),你在非叶子节点加边显然 不能最优的 消除所有割边。

所以答案就是 \(\displaystyle \left\lceil\frac{leaves}{2}\right\rceil\),其中 \(leaves\) 是叶子节点的数量(即只与一条边相连的节点数量)。

/**
  * author : OMG_78
  * created: 2023-07-13-10.49.45
 **/
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Memory_Begins;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
inline bool ok (char c) {
  if (c < '0') return true;
  if (c > '9') return true;
  return false;
}
template < class Z >
inline void read (Z &tmp) {
  Z x = 0, f = 0;
  char c = getchar ();
  for ( ; ok (c) ; c = getchar ()) f = (c == '-') ? 1 : f;
  for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
  tmp = !f ? x : -x;
}
const int N = 5e3 + 5, M = 1e4 + 5;
int n, m, eid = 1, ans, ck, dfn[N], low[N], head[N], bel[N], mp[N], bj[N];
struct Edge {int to, nxt;} ed[M << 1];
vector < PII > eds;
bool vis[M << 1];
typedef vector < int > vint;
vector < vint > st;
inline void add_edge (int u, int v) {
  eid ++;
  ed[eid].to = v;
  ed[eid].nxt = head[u];
  head[u] = eid;
}
inline void tarjan (int u, int id) {
  dfn[u] = low[u] = ++ ck;
  for (int i = head[u]; i ; i = ed[i].nxt) {
    int v = ed[i].to;
    if (!dfn[v]) {
      tarjan (v, i);
      if (dfn[u] < low[v]) vis[i] = vis[i ^ 1] = 1;
      low[u] = min (low[u], low[v]);
    }
    else if (i != (id ^ 1)) low[u] = min (low[u], dfn[v]);
  }
}
inline void dfs (int u, int blo) {
  bel[u] = blo;
  st[blo].push_back (u);
  for (int i = head[u]; i ; i = ed[i].nxt) {
    int v = ed[i].to;
    if (bel[v] || vis[i]) continue;
    dfs (v, blo);
  }
}
bool Memory_Ends;
signed main () {
  fprintf (stderr, "%.3lf MB\n", (&Memory_Begins - &Memory_Ends) / 1048576.0);
  read (n), read (m);
  for (int i = 1;i <= m; ++ i) {
    int u, v;
    read (u), read (v);
    add_edge (u, v);
    add_edge (v, u);
    eds.push_back (make_pair (u, v));
  }
  for (int i = 1;i <= n; ++ i) {
    if (!dfn[i]) tarjan (i, 0);
  }
  st.push_back (vint ());
  for (int i = 1;i <= n; ++ i) {
    if (!bel[i]) st.push_back (vint ()), ans ++, dfs (i, ans);
  }
  for (int i = 1;i <= ans; ++ i) {
    for (int j = 0;j < st[i].size (); ++ j) mp[st[i][j]] = i;
  }
  set < PII > stqwq;
  stqwq.clear ();
  for (PII e : eds) {
    int u = e.first, v = e.second;
    u = mp[u], v = mp[v];
    if (u != v) stqwq.insert (make_pair (u, v));
  }
  for (PII e : stqwq) bj[e.first] ++, bj[e.second] ++;
  int leaf = 0;
  for (int i = 1;i <= n; ++ i) leaf += (bj[i] == 1);
  leaf = (leaf + 1) / 2;
  printf ("%d\n", leaf);
  fprintf (stderr, "%.3lf ms\n", 1e3 * clock () / CLOCKS_PER_SEC);
  return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/

七、洛谷 P8867 [NOIP2022] 建造军营

https://www.luogu.com.cn/problem/P8867

边双连通分量 + 树形 DP 好题。

参考了你谷第一篇题解。()

容易发现 B 国只有摧毁了 A 国的割边才可能阻断两个军营的联系。

所以考虑将原图缩边双,然后形成一棵树,设缩完点之后节点 \(u\) 中含有 \(E_u\) 条边和 \(V_u\) 个节点。

既然缩点之后是一棵树,那么考虑树形 dp。

\(f_{u,0/1}\) 表示以 \(u\) 为根的子树是否有军营,最后计算出的方案数,如果有军营那么方案一定连边与 \(u\) 连通。

如果 \(u\) 的子树中没有军营,那么连到子节点的边连不连都行,转移方程:

\[f_{u, 0} = \prod_{v \in son_u} 2f_{v, 0} \]

如果 \(u\) 的子树还没有建造军营,那么就在 \(v\) 的子树建:方案数增加 \(f_{u, 0}f_{v, 1}\)

反之就随便:方案数乘上 \(2f_{v, 0} + f_{v, 1}\)

整理一下有:

按顺序 dp(\(f_{u, 0}, f_{u, 1}\) 一样的顺序,\(f_{u, 1}\) 转移方程中的 \(f_{u, 0}\) 是上一个 \(f_{u, 0}\),两者同步 dp),\(f_{u, 1} \leftarrow f_{u, 1}(2f_{v, 0} + f_{v, 1}) + f_{u, 0}f_{v, 1}\)

考虑一下边界,\(f_{u, 0}\) 的话只能看守道路,不能建造军营,方案数 \(2^{E_u}\)\(f_{u, 1}\) 至少要建造一个军营,也要考虑看守道路,所以方案数 \(2^{E_u + V_u} - 2^{E_u}\)

\(e_u\) 表示以 \(u\) 为根的子树内边的个数,具体可以树形 dp 求出。

考虑统计答案:容易发现建造的军营都可以被包含在一个子树中,设为 \(u\),并且钦定不选 \((u, fa_u)\)(如果选了 \((u, fa_u)\),那么答案在 \(fa_u\) 已经被累加),贡献即为 \(f_{u, 1} \times 2^{e_1 - e_u - [u \neq 1]}\),就做完了。

注意常数,这个 sb 写的时候被卡常了。

/**
  * author : OMG_78
  * created: 2023-07-14-09.18.17
 **/
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Memory_Begins;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
inline bool ok (char c) {
  if (c < '0') return true;
  if (c > '9') return true;
  return false;
}
template < class Z >
inline void read (Z &tmp) {
  Z x = 0, f = 0;
  char c = getchar ();
  for ( ; ok (c) ; c = getchar ()) f = (c == '-') ? 1 : f;
  for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
  tmp = !f ? x : -x;
}
const int N = 5e5 + 5, M = 1e6 + 5;
const ll mod = 1e9 + 7;
int n, m, eid = 1, ck, dfn[N], low[N], head[N], bel[N], cnts;
struct Edge {int to, nxt;} ed[M << 1];
bool vis[M << 1];
typedef vector < int > vint;
vector < vint > st;
inline void add_edge (int u, int v) {
  eid ++;
  ed[eid].to = v;
  ed[eid].nxt = head[u];
  head[u] = eid;
}
inline void tarjan (int u, int id) {
  dfn[u] = low[u] = ++ ck;
  for (int i = head[u]; i ; i = ed[i].nxt) {
    int v = ed[i].to;
    if (!dfn[v]) {
      tarjan (v, i);
      if (dfn[u] < low[v]) vis[i] = vis[i ^ 1] = 1;
      low[u] = min (low[u], low[v]);
    }
    else if (i != (id ^ 1)) low[u] = min (low[u], dfn[v]);
  }
}
inline void dfs (int u, int blo) {
  bel[u] = blo;
  st[blo].push_back (u);
  for (int i = head[u]; i ; i = ed[i].nxt) {
    int v = ed[i].to;
    if (bel[v] || vis[i]) continue;
    dfs (v, blo);
  }
}
vector < int > tr[N];
int E[N], V[N], sum[N];
ll dp[N][2], ans, pw[1500025];
inline void pre (int u, int fa) {
  sum[u] = E[u];
  for (int v : tr[u]) {
    if (v == fa) continue;
    pre (v, u);
    sum[u] += sum[v] + 1;
  }
}
inline void solve (int u, int fa) {
  for (int v : tr[u]) {
    if (v == fa) continue;
    solve (v, u);
    dp[u][1] = (dp[u][0] * dp[v][1] + dp[u][1] * (2 * dp[v][0] + dp[v][1])) % mod;
    dp[u][0] = (dp[u][0] * dp[v][0] * 2) % mod;
  }
  if (u == 1) ans += dp[u][1];
  else ans = (ans + dp[u][1] * pw[sum[1] - sum[u] - 1]) % mod;
}
vector < PII > edqwq;
bool Memory_Ends;
signed main () {
//  freopen ("barrack4.in", "r", stdin);
//  freopen ("barrack4.out", "w", stdout);
//  fprintf (stderr, "%.3lf MB\n", (&Memory_Begins - &Memory_Ends) / 1048576.0);
  pw[0] = 1;
  for (int i = 1;i < 1500025; ++ i) pw[i] = pw[i - 1] * 2 % mod;
  read (n), read (m);
  for (int i = 1;i <= m; ++ i) {
    int u, v;
    read (u), read (v);
    add_edge (u, v);
    add_edge (v, u);
    edqwq.push_back (make_pair (u, v));
  }
  for (int i = 1;i <= n; ++ i) {
    if (!dfn[i]) tarjan (i, 0);
  }
  st.push_back (vint ());
  for (int i = 1;i <= n; ++ i) {
    if (!bel[i]) st.push_back (vint ()), cnts ++, dfs (i, cnts);
  }
  set < PII > st;
  st.clear ();
  for (PII qwq : edqwq) {
    int u = qwq.first;
    int v = qwq.second;
    u = bel[u], v = bel[v];
    if (u != v) {
      st.insert (make_pair (u, v));
      st.insert (make_pair (v, u));
    }
    else E[u] ++;
  }
  for (PII qwq : st) {
    int u = qwq.first;
    int v = qwq.second;
    tr[u].push_back (v);
  }
  for (int i = 1;i <= n; ++ i) V[bel[i]] ++;
  for (int i = 1;i <= cnts; ++ i) {
    dp[i][0] = pw[E[i]];
    dp[i][1] = (pw[E[i] + V[i]] - dp[i][0]) % mod;
    dp[i][1] = (dp[i][1] + mod) % mod;
  }
  pre (1, 0);
  solve (1, 0);
  printf ("%lld\n", ans % mod);
//  fprintf (stderr, "%.3lf ms\n", 1e3 * clock () / CLOCKS_PER_SEC);
  return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/

八、洛谷 P3469 [POI2008] BLO-Blockade

https://www.luogu.com.cn/problem/P3469

只需要在跑 tarjan 的时候再加上一个步骤就行了。

容易发现一个大小为 \(siz\) 的连通块对答案的贡献为 \(n \times (n - siz)\),因为乘法原理。

然后在判 \(u\) 为割点的时候(假设遍历到 \(v\)),那么就在 cut 掉 \(u\) 的贡献里加上 \(siz_v \times (n - siz_v)\)

如果 \(u\) 不是割点,那么答案就是 \(2 \times (n - 1)\)(因为 \(u\) 和剩下的节点被分开了)。

如果 \(u\) 是割点,还要加上 \(n - 1 + sum \times (n - sum)\),其中 \(n - 1\)\(u\) 这个单独的连通块的贡献,\(sum\) 是已经被分开的连通块的大小总和(包括 \(u\)),正确性显然。

注意要把贡献加全,别忘了开 long long。

/**
  * author : OMG_78
  * created: 2023-07-14-11.57.36
 **/
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Memory_Begins;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
inline bool ok (char c) {
  if (c < '0') return true;
  if (c > '9') return true;
  return false;
}
template < class Z >
inline void read (Z &tmp) {
  Z x = 0, f = 0;
  char c = getchar ();
  for ( ; ok (c) ; c = getchar ()) f = (c == '-') ? 1 : f;
  for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
  tmp = !f ? x : -x;
}
const int N = 1e5 + 5;
int dfn[N], low[N], vis[N], cut[N];
ll siz[N];
ll ans[N];
int res, ck, n, m;
vector < int > G[N];
inline void tarjan (int u, int fa) {
  vis[u] = siz[u] = 1;
  dfn[u] = low[u] = ++ ck;
  int son = 0;
  ll solved = 1;
  for (int v : G[u]) {
    if (!vis[v]) {
      son ++;
      tarjan (v, u);
      siz[u] += siz[v];
      low[u] = min (low[u], low[v]);
      if (low[v] >= dfn[u]) {
        cut[u] = 1; res ++;
        ans[u] += siz[v] * ((1ll * n) - siz[v]);
        solved += siz[v];
      }
    }
    else if (v != fa) {
      low[u] = min (low[u], dfn[v]);
    }
  }
  if (fa == u && son > 1 && !cut[u]) {
    cut[u] = 1; res ++;
  }
  if (!cut[u]) ans[u] = 2 * (n - 1);
  else ans[u] += (1ll * n - 1ll) + solved * (1ll * n - solved);
}
bool Memory_Ends;
signed main () {
  fprintf (stderr, "%.3lf MB\n", (&Memory_Begins - &Memory_Ends) / 1048576.0);
  read (n), read (m);
  for (int i = 1;i <= m; ++ i) {
    int u, v;
    read (u), read (v);
    G[u].push_back (v);
    G[v].push_back (u);
  }
  for (int i = 1;i <= n; ++ i) {
    if (!vis[i]) {
      ck = 0;
      tarjan (i, i);
    }
  }
  for (int i = 1;i <= n; ++ i) printf ("%lld\n", ans[i]);
  fprintf (stderr, "%.3lf ms\n", 1e3 * clock () / CLOCKS_PER_SEC);
  return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/

九、洛谷 P3225 [HNOI2012] 矿场搭建

https://www.luogu.com.cn/problem/P3225

2 倍经验:https://www.luogu.com.cn/problem/SP16185

3 倍经验:https://www.luogu.com.cn/problem/UVA1108

提示:

站外双倍经验数据范围的 \(n\) 开到 \(5e4 + 10\)!!!不写数据范围真 *!!!

站外双倍经验数据范围的 \(n\) 开到 \(5e4 + 10\)!!!不写数据范围真 *!!!

站外双倍经验数据范围的 \(n\) 开到 \(5e4 + 10\)!!!不写数据范围真 *!!!

对于每一个点双连通分量分类讨论:

  • 没有割点。建 \(2\) 个,防止其中 \(1\) 个崩塌。

  • \(1\) 个割点。建 \(1\) 个,这个出口崩塌可以通过割点跑到别的点双连通分量,否则通过这个出口逃跑。

  • \(\geq 2\) 个割点。不建,至少有 \(1\) 个割点可以跑到别的点双连通分量。

然后乘法原理计数即可,记得开 unsigned long long。

/**
  * author : OMG_78
  * created: 2023-07-14-12.37.18
 **/
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Memory_Begins;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
inline bool ok (char c) {
  if (c < '0') return true;
  if (c > '9') return true;
  return false;
}
template < class Z >
inline void read (Z &tmp) {
  Z x = 0, f = 0;
  char c = getchar ();
  for ( ; ok (c) ; c = getchar ()) f = (c == '-') ? 1 : f;
  for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
  tmp = !f ? x : -x;
}
int n, m;
namespace cut_vertex {
  const int N = 5e4 + 25;
  int dfn[N], low[N], vis[N], cut[N];
  int res, ck;
  vector < int > G[N];
  inline void cls () {
    for (int i = 1;i <= 5e4 + 10; ++ i) dfn[i] = low[i] = vis[i] = cut[i] = 0;
    res = ck = 0;
    for (int i = 1;i <= 5e4 + 10; ++ i) G[i].clear ();
  }
  inline void tarjan (int u, int fa) {
    vis[u] = 1;
    dfn[u] = low[u] = ++ ck;
    int son = 0;
    for (int v : G[u]) {
      if (!vis[v]) {
        son ++;
        tarjan (v, u);
        low[u] = min (low[u], low[v]);
        if (fa != u && low[v] >= dfn[u] && !cut[u]) {
          cut[u] = 1; res ++;
        }
      }
      else if (v != fa) {
        low[u] = min (low[u], dfn[v]);
      }
    }
    if (fa == u && son > 1 && !cut[u]) {
      cut[u] = 1; res ++;
    }
  }
  inline set < int > solve () {
    for (int i = 1;i <= n; ++ i) {
      if (!vis[i]) {
        ck = 0;
        tarjan (i, i);
      }
    }
    set < int > ans;
    ans.clear ();
    for (int i = 1;i <= n; ++ i) {
      if (cut[i]) ans.insert (i);
    }
    return ans;
  }
}
namespace biconnected_component {
  const int N = 5e4 + 25, M = 5e4 + 5;
  int stk[M << 1], tp, cnt, low[N], dfn[N], ck;
  vector < int > st[N], G[N];
  inline void cls () {
    for (int i = 0;i < (M << 1); ++ i) stk[i] = 0;
    for (int i = 1;i <= 5e4 + 10; ++ i) low[i] = dfn[i] = 0;
    tp = cnt = ck = 0;
    for (int i = 1;i <= 5e4 + 10; ++ i) st[i].clear (), G[i].clear ();
  }
  inline void tarjan (int u, int fa) {
    int son = 0;
    low[u] = dfn[u] = ++ ck;
    stk[++ tp] = u;
    for (int v : G[u]) {
      if (!dfn[v]) {
        son ++;
        tarjan (v, u);
        low[u] = min (low[u], low[v]);
        if (low[v] >= dfn[u]) {
          cnt ++;
          while (stk[tp + 1] != v) st[cnt].push_back (stk[tp --]);
          st[cnt].push_back (u);
        }
      }
      else if (v != fa) low[u] = min (low[u], dfn[v]);
    }
    if (!fa && !son) st[++ cnt].push_back (u);
  }
  inline void solve () {
    for (int i = 1;i <= n; ++ i) {
      if (!dfn[i]) {
        tp = 0;
        tarjan (i, 0);
      }
    }
  }
}
int problemprovidercreep;
bool Memory_Ends;
signed main () {
  fprintf (stderr, "%.3lf MB\n", (&Memory_Begins - &Memory_Ends) / 1048576.0);
  while (true) {
    problemprovidercreep ++;
    read (m);
    if (!m) break;
    cut_vertex :: cls ();
    biconnected_component :: cls ();
    n = 0;
    for (int i = 1;i <= m; ++ i) {
      int u, v;
      read (u), read (v);
      cut_vertex :: G[u].push_back (v);
      cut_vertex :: G[v].push_back (u);
      biconnected_component :: G[u].push_back (v);
      biconnected_component :: G[v].push_back (u);
      n = max (n, u);
      n = max (n, v);
    }
    set < int > ve = cut_vertex :: solve ();
    unsigned long long ans2 = 1;
    int ans1 = 0;
    biconnected_component :: solve ();
    for (int i = 1;i <= biconnected_component :: cnt; ++ i) {
      int ct = 0;
      for (int v : (biconnected_component :: st[i])) {
        if (ve.count (v)) ct ++;
      }
      if (ct == 1) {
        ans1 ++;
        int tmp = (biconnected_component :: st[i]).size ();
        tmp --;
        unsigned long long kd = (unsigned long long) tmp;
        ans2 *= kd;
      }
      if (!ct) {
        ans1 += 2;
        int tmp = (biconnected_component :: st[i]).size ();
        unsigned long long kd = (unsigned long long) tmp;
        tmp --;
        kd *= (unsigned long long) tmp;
        kd >>= 1;
        ans2 *= kd;
      }
    }
    printf ("Case %d: %d %llu\n", problemprovidercreep, ans1, ans2);
  }
  fprintf (stderr, "%.3lf ms\n", 1e3 * clock () / CLOCKS_PER_SEC);
  return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/
posted @ 2023-07-11 21:01  CountingGroup  阅读(17)  评论(0编辑  收藏  举报