CF1477D Nezzar and Hidden Permutations 题解

Description

给定一张 \(n\) 个点 \(m\) 条边的简单无向图,构造两个排列 \(p,q\),使得:

  • 对任意 \((u,v)\in E\)\((p_u-p_v)(q_u-q_v)>0\).
  • 在此基础上,最大化 \(\left|\left\{i\ |\ p_i\neq q_i\right\}\right|\).

\(1\leq n,m\leq 5\times 10^5\)

Solution

首先显然如果存在一个点 \(x\) 的度数等于 \(n-1\),则将边定向后这个点的拓扑序一定唯一,所以这个位置一定会相同,可以把 \(p_x,q_x\) 赋值为 \(n\),然后将这个点删掉。

那么剩下的点一定满足度数 \(\leq n-2\),可以猜测剩下的点一定没有位置相同。

考虑把现在的图的补图建出来,则一定构成若干个大小不小于 \(2\) 的连通块。

如果补图是恰好一个菊花图,设菊花心为 \(x\),其它点为 \(a_1,a_2,\ldots,a_{n-1}\),让 \(p_x=1,p_{a_i}=i+1,q_x=n,q_{a_i}=i\) 可以满足条件且没有位置相同。

这启发我们对于一般情况去把补图划分成若干个大小不小于 \(2\) 的菊花图。

下面给出构造:

  1. 从前往后扫 \(i\),如果 \(i\) 已经被划分进菊花图了,就不管。
  2. 否则遍历 \(i\) 的邻域,如果存在点 \(j\) 使得 \(j\) 没被划分进菊花图,就让 \(i\)\(i\) 邻域所有没被划分的点放到一个菊花图里。
  3. 如果找不到这样的 \(j\),就随便取 \(i\) 邻域的一个点 \(k\),如果 \(k\) 所在菊花大小等于 \(2\),则将 \(k\) 所在菊花的菊花心设为 \(k\),并对 \(k\)\(2\) 过程。
  4. 如果菊花大小大于 \(2\),就把 \(k\) 从原来的菊花里踢出,然后和 \(i\) 组成菊花。根据上面的构造方式,一定能保证 \(k\) 不是原来所在菊花的心,所以构造方式仍然合法。

注意补图可能边数很多,这里只需要找到补图的一个生成树,找生成树时就维护一个 set 表示目前没被遍历的点,每次拓展时就暴力遍历 set,找到不与当前点在原图上有边的点,并在补图上连边即可。

时间复杂度:\(O((n+m)\log n)\)

Code

#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 5e5 + 5;

int n, m, cnt;
int u[kMaxN], v[kMaxN], p[kMaxN], q[kMaxN], deg[kMaxN], bel[kMaxN];
bool del[kMaxN];
std::vector<int> G[kMaxN], T[kMaxN];
std::set<int> chry[kMaxN];

void init() {
  cnt = 0;
  for (int i = 1; i <= n; ++i)
    G[i].clear(), T[i].clear(), chry[i].clear(), p[i] = q[i] = deg[i] = bel[i] = del[i] = 0;
}

void solve1() { // 求出度数为 n - 1 的点
  std::set<std::pair<int, int>> st;
  for (int i = 1; i <= n; ++i) st.emplace(deg[i] = G[i].size(), i);
  int now = n;
  for (int i = 1; i <= n; ++i) {
    if (prev(st.end())->first != now - 1) break;
    int u = prev(st.end())->second;
    st.erase(prev(st.end()));
    p[u] = q[u] = ++cnt, --now, del[u] = 1;
    for (auto v : G[u]) {
      if (!del[v]) {
        st.erase({deg[v], v});
        --deg[v];
        st.emplace(deg[v], v);
      }
    }
  }
  for (int i = 1; i <= n; ++i) G[i].clear();
  for (int i = 1; i <= m; ++i)
    if (!del[u[i]] && !del[v[i]])
      G[u[i]].emplace_back(v[i]), G[v[i]].emplace_back(u[i]);
}

void solve2() { // 求出补图生成森林
  std::set<int> st;
  for (int i = 1; i <= n; ++i) {
    if (!del[i]) st.emplace(i);
    std::sort(G[i].begin(), G[i].end());
  }
  std::function<void(int)> dfs = [&] (int u) {
    std::vector<int> vec;
    for (auto v : st)
      if (std::find(G[u].begin(), G[u].end(), v) == G[u].end())
        vec.emplace_back(v), T[u].emplace_back(v), T[v].emplace_back(u);
    for (auto v : vec) st.erase(v);
    for (auto v : vec) dfs(v);
  };
  for (int i = 1; i <= n; ++i) {
    if (!del[i] && st.count(i)) {
      st.erase(i);
      dfs(i);
    }
  }
}

void solve3() { // 求出菊花划分
  for (int i = 1; i <= n; ++i) {
    if (!del[i] && !bel[i]) {
      bool fl = 0;
      for (auto j : T[i]) fl |= !bel[j];
      if (fl) {
        chry[i].emplace(i), bel[i] = i;
        for (auto j : T[i]) {
          if (!bel[j]) chry[i].emplace(j), bel[j] = i;
        }
      } else {
        int j = T[i][0];
        if (chry[bel[j]].size() == 2) {
          chry[j].swap(chry[bel[j]]);
          bel[bel[j]] = j;
          for (auto x : T[j]) {
            if (!bel[x]) chry[j].emplace(x), bel[x] = j;
          }
        } else {
          chry[bel[j]].erase(j);
          bel[i] = bel[j] = i;
          chry[i].emplace(i), chry[i].emplace(j);
        }
      }
    }
  }
  int sum = cnt;
  for (int i = 1; i <= n; ++i) sum += chry[i].size();
  for (int i = 1; i <= n; ++i) {
    if (chry[i].size()) {
      p[i] = cnt + 1;
      int now = 1;
      for (auto x : chry[i]) {
        if (x != i) p[x] = cnt + (++now);
      }
      q[i] = cnt + now;
      now = 0;
      for (auto x : chry[i]) {
        if (x != i) q[x] = cnt + (++now);
      }
      cnt += chry[i].size();
    }
  }
}

void dickdreamer() {
  std::cin >> n >> m;
  init();
  for (int i = 1; i <= m; ++i) {
    std::cin >> u[i] >> v[i];
    G[u[i]].emplace_back(v[i]), G[v[i]].emplace_back(u[i]);
  }
  solve1(), solve2(), solve3();
  for (int i = 1; i <= n; ++i) std::cout << p[i] << " \n"[i == n];
  for (int i = 1; i <= n; ++i) std::cout << q[i] << " \n"[i == n];
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}
posted @ 2024-12-20 21:59  下蛋爷  阅读(16)  评论(0)    收藏  举报