仙人掌&圆方树

1. 仙人掌

无向连通图,每条边要么不在环里,要么只在一个环里。

image

2. 圆方树

仙人掌 \(G=(V,E)\) 的圆方树 \(T=(V_T,E_T)\) 为满足以下条件的无向图:

  • \(V_T=R_T∪S_T\)\(R_T=V\)\(R_T∩S_T=∅\),其中 \(R_T\) 集合表示圆点,\(S_T\) 集合表示方点;
  • \(∀e∈E\),若 \(e\) 不在任何简单环中,则 \(e∈E_T\)
  • 对于每个仙人掌中的简单环 \(R\),存在方点 \(p_R∈S_T\),并且 \(∀p∈R\) 满足 \((p_R,p)∈E_T\),即对每个环建方点连所有点

简单来说,就是把所有环拆开,新建一个方点,连向所有环上点

image

代码:

void dfs(int x, int y) {
  dfn[x] = ++tot;
  fa[x] = y;
  for (int i = head[x][0]; i; i = nxt[i]) {
    if (to[i] == y) continue;
    if (!dfn[to[i]]) {
      mark[x] = 0;
      dfs(to[i], x);
      if (!mark[x]) add(x, to[i], 1);
    } else {
      if (dfn[to[i]] > dfn[x]) continue;
      int u = x;
      ++num;
      while (u != to[i]) {
        add(num, u, 1);
        u = fa[u];
        mark[u] = 1;
      }
      add(to[i], num, 1);
    }
  }
}

3. 题

1. [BZOJ4316] 小 C 的独立集/洛谷U219357 xxqz 的独立集

做法:

考虑树的独立集,树形 dp,\(f[i][1/0]\) 表示节点 \(i\) 选/不选子树最大独立集,暴力转移。

变成仙人掌:圆点和圆点之间正常转移,遇到环直接把与根连接的两端拆开变成链,暴力 dp 把链两端的四种状态求出,再转移到根。

代码:

// Problem: U219357 xxqz 的独立集
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/U219357
// Memory Limit: 128 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using namespace std;
namespace Std {
int n, m, head[100010][2], nxt[400010], to[400010], cnt, dfn[100010],
    fa[100010], tot, num, f[100010][2], g[100010][2];
bool mark[100010];
inline void add(int x, int y, bool z) {
  to[++cnt] = y;
  nxt[cnt] = head[x][z];
  head[x][z] = cnt;
}
void dfs(int x, int y) {
  dfn[x] = ++tot;
  fa[x] = y;
  for (int i = head[x][0]; i; i = nxt[i]) {
    if (to[i] == y) continue;
    if (!dfn[to[i]]) {
      mark[x] = 0;
      dfs(to[i], x);
      if (!mark[x]) add(x, to[i], 1);
    } else {
      if (dfn[to[i]] > dfn[x]) continue;
      int u = x;
      ++num;
      while (u != to[i]) {
        add(num, u, 1);
        u = fa[u];
        mark[u] = 1;
      }
      add(to[i], num, 1);
    }
  }
}
void dp(int x) {
  f[x][1] = 1;
  for (int i = head[x][1]; i; i = nxt[i]) {
    if (to[i] <= n) {
      dp(to[i]);
      f[x][1] += f[to[i]][0];
      f[x][0] += max(f[to[i]][1], f[to[i]][0]);
    } else {
      int u = to[i], f1, f2, f3, f4;
      for (int j = head[u][1]; j; j = nxt[j]) dp(to[j]);
      tot = 0;
      g[0][0] = f[to[head[u][1]]][0];
      g[0][1] = 0xc3c3c3c3;
      for (int j = head[u][1]; j; j = nxt[j]) {
        if (j == head[u][1]) continue;
        ++tot;
        g[tot][1] = f[to[j]][1] + g[tot - 1][0];
        g[tot][0] = f[to[j]][0] + max(g[tot - 1][0], g[tot - 1][1]);
      }
      f1 = g[tot][0];
      f2 = g[tot][1];
      g[0][0] = 0xc3c3c3c3;
      g[0][1] = f[to[head[u][1]]][1];
      tot = 0;
      for (int j = head[u][1]; j; j = nxt[j]) {
        if (j == head[u][1]) continue;
        ++tot;
        g[tot][1] = f[to[j]][1] + g[tot - 1][0];
        g[tot][0] = f[to[j]][0] + max(g[tot - 1][0], g[tot - 1][1]);
      }
      f3 = g[tot][0];
      f4 = g[tot][1];
      f[x][0] += max(max(f1, f2), max(f3, f4));
      f[x][1] += f1;
    }
  }
}
int main() {
  scanf("%d%d", &n, &m);
  num = n;
  int u, v;
  for (int i = 1; i <= m; ++i) {
    scanf("%d%d", &u, &v);
    add(u, v, 0);
    add(v, u, 0);
  }
  dfs(1, 0);
  dp(1);
  printf("%d\n", max(f[1][0], f[1][1]));
  return 0;
}
}  // namespace Std
int main() { return Std::main(); }

2. 洛谷P5236 【模板】静态仙人掌

做法:

树上做法:直接暴力 \(\text{LCA}\) 即可。

转化为仙人掌:先建出圆方树,然后对于 \(\text{LCA}\) 是圆点的情况和树的一样,如果 \(\text{LCA}\) 是方点,重新把每个点跳到所属环,求一下两点的换上距离即可。

代码:

// Problem: P5236 【模板】静态仙人掌
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P5236
// Memory Limit: 125 MB
// Time Limit: 300 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using namespace std;
namespace Std {
int n, m, head[20010][2], nxt[80010], to[80010], cnt, dfn[20010], fa[20010],
    tot, num, len[80010], q, dep[20010], d[20010][20], f[20010][20], top[20010],
    lenh[20010], dis[20010];
bool mark[20010];
inline void add(int x, int y, int z, bool t) {
  to[++cnt] = y;
  len[cnt] = z;
  nxt[cnt] = head[x][t];
  head[x][t] = cnt;
}
void dfs(int x, int y) {
  dfn[x] = ++tot;
  fa[x] = f[x][0] = y;
  for (int i = head[x][0]; i; i = nxt[i]) {
    if (to[i] == y) continue;
    if (!dfn[to[i]]) {
      mark[x] = 0;
      dis[to[i]] = dis[x] + len[i];
      d[to[i]][0] = len[i];
      dfs(to[i], x);
      if (!mark[x]) add(x, to[i], len[i], 1);
    } else {
      if (dfn[to[i]] > dfn[x]) continue;
      int u = x;
      ++num;
      lenh[num] = dis[x] - dis[to[i]] + len[i];
      while (u != to[i]) {
        f[u][0] = num;
        d[u][0] = min(dis[u] - dis[to[i]], lenh[num] - dis[u] + dis[to[i]]);
        add(num, u, d[u][0], 1);
        u = fa[u];
        mark[u] = 1;
      }
      f[num][0] = to[i];
      d[num][0] = 0;
      add(to[i], num, 0, 1);
    }
  }
}
void dfs2(int x) {
  for (int i = head[x][1]; i; i = nxt[i]) {
    dep[to[i]] = dep[x] + 1;
    dfs2(to[i]);
  }
}
void init(void) {
  for (int i = 1; i < 20; ++i) {
    for (int j = 1; j <= num; ++j) {
      f[j][i] = f[f[j][i - 1]][i - 1];
      d[j][i] = d[j][i - 1] + d[f[j][i - 1]][i - 1];
    }
  }
}
int query(int x, int y) {
  int u = x, v = y, ans = 0;
  if (dep[u] < dep[v]) swap(u, v);
  for (int i = 19; ~i; --i) {
    if (dep[f[u][i]] < dep[v]) continue;
    ans += d[u][i];
    u = f[u][i];
  }
  if (u != v) {
    for (int i = 19; ~i; --i) {
      if (f[u][i] == f[v][i]) continue;
      ans += d[u][i] + d[v][i];
      u = f[u][i], v = f[v][i];
    }
    if (u != v) {
      if (dep[u] == dep[v]) {
        ans += d[u][0] + d[v][0];
        u = f[u][0], v = f[v][0];
      } else {
        if (dep[u] > dep[v]) {
          ans += d[u][0];
          u = f[u][0];
        } else {
          ans += d[v][0];
          v = f[v][0];
        }
      }
    }
  }
  if (u > n) {
    ans = 0;
    for (int i = 19; ~i; --i) {
      if (dep[f[x][i]] > dep[u]) {
        ans += d[x][i];
        x = f[x][i];
      }
      if (dep[f[y][i]] > dep[u]) {
        ans += d[y][i];
        y = f[y][i];
      }
    }
    ans += min(abs(dis[x] - dis[y]), lenh[u] - abs(dis[x] - dis[y]));
  }
  return ans;
}
int main() {
  scanf("%d%d%d", &n, &m, &q);
  num = n;
  int u, v, w;
  for (int i = 1; i <= m; ++i) {
    scanf("%d%d%d", &u, &v, &w);
    add(u, v, w, 0);
    add(v, u, w, 0);
  }
  dep[1] = 1;
  dfs(1, 0);
  dfs2(1);
  init();
  for (int i = 1; i <= q; ++i) {
    scanf("%d%d", &u, &v);
    printf("%d\n", query(u, v));
  }
  return 0;
}
}  // namespace Std
int main() { return Std::main(); }

3. P4244 [SHOI2008]仙人掌图 II

做法:

树上做法有两种,一种是两遍 \(\text{dfs}\) 的做法,一种是 \(\text{dp}\) 做法,两种方法都很简单。

换成仙人掌:贪心做法换到仙人掌上来做并不容易,考虑 \(\text{dp}\)。设 \(f[i][0/1]\) 表示节点 \(i\) 为一端子树内最长/次长链长度,圆点转移很简单,遇到环,直接环形 \(\text{dp}\),求出环对总答案的贡献,再利用环上点到环的根节点距离求出根节点的 \(f[i][0/1]\)

代码:

// Problem: P4244 [SHOI2008]仙人掌图 II
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P4244
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
#define add(x, y, z) to[x][z].push_back(y)
using namespace std;
namespace Std {
inline bool maxy(int &x, int y) { return (x = max(x, y)); }
int n, m, num, stack[100010], top, fa[100010], dfn[100010], tot, f[100010][2],
    len[100010], q1[100010], q2[100010], ans;
vector<int> to[100010][2];
bool mark[100010];
void dfs1(int x) {
  dfn[x] = ++tot;
  for (auto i : to[x][0]) {
    if (i == fa[x]) continue;
    if (!dfn[i]) {
      mark[x] = false;
      fa[i] = x;
      dfs1(i);
      if (!mark[x]) add(x, i, 1);
    } else {
      if (dfn[i] > dfn[x]) continue;
      ++num;
      add(i, num, 1);
      int u = x;
      while (u != i) {
        add(num, u, 1);
        u = fa[u];
        mark[u] = true;
      }
    }
  }
}
void dfs2(int x) {
  for (auto i : to[x][1]) {
    if (i <= n) {
      dfs2(i);
      if (f[i][0] + 1 >= f[x][0]) {
        if (f[x][0] >= f[x][1]) {
          f[x][1] = f[x][0];
        }
        f[x][0] = f[i][0] + 1;
      } else if (f[i][0] + 1 >= f[x][1])
        f[x][1] = f[i][0] + 1;
    } else {
      int u = i;
      for (auto j : to[u][1]) {
        dfs2(j);
      }
      top = 0;
      for (auto j : to[u][1]) {
        stack[++top] = j;
      }
      stack[++top] = x;
      int l1 = 1, r1 = 0, l2 = 1, r2 = 0;
      for (int j = 1; j <= (top >> 1); ++j) {
        while (l1 <= r1 && j + f[stack[j]][0] > q1[r1] + f[stack[q1[r1]]][0])
          --r1;
        q1[++r1] = j;
      }
      for (int j = (top >> 1) + 1; j < top; ++j) {
        while (l2 <= r2 && f[stack[j]][0] - j > f[stack[q2[r2]]][0] - q2[r2])
          --r2;
        q2[++r2] = j;
      }
      for (int j = 1; j < top - 1; ++j) {
        if (j + (top >> 1) < top) {
          if (q2[l2] == j + (top >> 1)) ++l2;
          while (l1 <= r1 && j + (top >> 1) + f[stack[j + (top >> 1)]][0] >
                                 q1[r1] + f[stack[q1[r1]]][0])
            --r1;
          q1[++r1] = j + (top >> 1);
        }
        if (q1[l1] == j) ++l1;
        if (l2 <= r2)
          maxy(ans,
               max(q1[l1] + f[stack[q1[l1]]][0] + f[stack[j]][0] - j,
                   f[stack[j]][0] + j + top - q2[l2] + f[stack[q2[l2]]][0]));
        else
          maxy(ans, q1[l1] + f[stack[q1[l1]]][0] + f[stack[j]][0] - j);
      }
      int maxn = 0;
      for (int j = 1; j < top; ++j) {
        maxn = max(maxn, f[stack[j]][0] + min(j, top - j));
      }
      if (maxn >= f[x][0]) {
        if (f[x][0] >= f[x][1]) {
          f[x][1] = f[x][0];
        }
        f[x][0] = maxn;
      } else if (maxn >= f[x][1])
        f[x][1] = maxn;
    }
  }
  maxy(ans, f[x][1] + f[x][0]);
}
int main() {
  scanf("%d%d", &n, &m);
  for (int i = 1; i <= m; ++i) {
    int t, u, v;
    scanf("%d", &t);
    if (t) scanf("%d", &u);
    for (int j = 1; j < t; ++j) {
      scanf("%d", &v);
      add(u, v, 0);
      add(v, u, 0);
      u = v;
    }
  }
  num = n;
  dfs1(1);
  dfs2(1);
  printf("%d\n", ans);
  return 0;
}
}  // namespace Std
int main() { return Std::main(); }

4. P3180 [HAOI2016]地图

做法:

考虑树的做法,线段树合并板子题,直接暴力合并即可。

变成仙人掌,我们发现,环上的点分为两类,与全图根节点最近的一个点是一类,其余点是另一类,对于第一类点,环上其余所有点都可以视为其子树范围内,对于第二类点,环上其余所有点都不包含在其子树范畴。所以原图可以转化为第一类点向第二类点连边,这样就变成了树的问题。

代码:

// Problem: P3180 [HAOI2016]地图
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3180
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
#define add(x, y, z) to[x][z].push_back(y)
using namespace std;
namespace Std {
int n, m, cnt, dfn[100010], fa[100010], tot, q, num[100010], po[100010],
    type[100010], li[100010], ans[100010];
bool mark[100010];
vector<int> vec[100010], to[100010][2];
struct tree {
  tree *ls, *rs;
  int num1, num2;
  tree() {
    ls = rs = NULL;
    num1 = num2 = 0;
  }
  void pushup() {
    if (ls == NULL) {
      num1 = rs->num1;
      num2 = rs->num2;
    } else if (rs == NULL) {
      num1 = ls->num1;
      num2 = ls->num2;
    } else {
      num1 = ls->num1 + rs->num1;
      num2 = ls->num2 + rs->num2;
    }
  }
  void change(int l, int r, int pos) {
    if (l == r) {
      if ((!num1 && (!num2))) num2 = 1;
      if (num1)
        num1 = 0, num2 = 1;
      else
        num2 = 0, num1 = 1;
      return;
    }
    int mid = ((l + r) >> 1);
    if (pos <= mid) {
      if (ls == NULL) ls = new tree;
      ls->change(l, mid, pos);
    } else {
      if (rs == NULL) rs = new tree;
      rs->change(mid + 1, r, pos);
    }
    pushup();
  }
  void merge(tree *x, int l, int r) {
    if (l == r) {
      if (x->num1) {
        if (num1)
          num1 = 0, num2 = 1;
        else
          num1 = 1, num2 = 0;
      }
      return;
    }
    int mid = (l + r) >> 1;
    if (ls != NULL && x->ls != NULL)
      ls->merge(x->ls, l, mid);
    else if (x->ls != NULL)
      ls = x->ls;
    if (rs != NULL && x->rs != NULL)
      rs->merge(x->rs, mid + 1, r);
    else if (x->rs != NULL)
      rs = x->rs;
    pushup();
  }
  int query(int l, int r, int L, int R, int opt) {
    if (L <= l && r <= R) return opt ? num1 : num2;
    int tmp = 0;
    int mid = (l + r) >> 1;
    if (L <= mid && ls != NULL) tmp += ls->query(l, mid, L, R, opt);
    if (R > mid && rs != NULL) tmp += rs->query(mid + 1, r, L, R, opt);
    return tmp;
  }
} * rt[100010];
void dfs(int x, int y) {
  dfn[x] = ++tot;
  fa[x] = y;
  for (auto i : to[x][0]) {
    if (i == y) continue;
    if (!dfn[i]) {
      mark[x] = 0;
      dfs(i, x);
      if (!mark[x]) add(x, i, 1);
    } else {
      if (dfn[i] > dfn[x]) continue;
      int u = x;
      while (u != i) {
        add(i, u, 1);
        u = fa[u];
        mark[u] = 1;
      }
    }
  }
}
void dfs2(int x) {
  rt[x] = new tree;
  rt[x]->change(1, 1000000, num[x]);
  for (auto i : to[x][1]) {
    dfs2(i);
    rt[x]->merge(rt[i], 1, 1000000);
  }
  for (auto i : vec[x]) {
    ans[i] = rt[x]->query(1, 1000000, 1, li[i], type[i]);
  }
}
int main() {
  scanf("%d%d", &n, &m);
  int u, v;
  for (int i = 1; i <= n; ++i) {
    scanf("%d", num + i);
  }
  for (int i = 1; i <= m; ++i) {
    scanf("%d%d", &u, &v);
    add(u, v, 0);
    add(v, u, 0);
  }
  dfs(1, 0);
  scanf("%d", &q);
  for (int i = 1; i <= q; ++i) {
    scanf("%d%d%d", type + i, po + i, li + i);
    vec[po[i]].push_back(i);
  }
  dfs2(1);
  for (int i = 1; i <= q; ++i) printf("%d\n", ans[i]);
  return 0;
}
}  // namespace Std
int main() { return Std::main(); }
posted @ 2022-06-12 15:51  Wilson_Inversion  阅读(121)  评论(0编辑  收藏  举报