「COCI 2018.10.20」Teoretičar

题目链接

如果知道 X X X 可能有一些网络流的做法,但是在此题中网络流也显得不够高效。设最大度数为 D D D,可以构造找到一个 X = 2 ⌈ log ⁡ 2 D ⌉ X=2^{\lceil \log_2 D \rceil} X=2log2D 的解:

假如 D = 1 D=1 D=1,全都是一种颜色。
否则我们可以考虑将所有的边划分成两个集合,每个边集都最多 ⌈ D 2 ⌉ \left\lceil \frac D2 \right\rceil 2D 条边连接一个节点:

  • 一个个加边,一旦有环,因为在二分图上,必然是偶环,交替地将一半放入 S S S,一半放入 T T T。剩下的一个森林,将剩下的度数控制住放入集合就更加简单了,dfs 的时候记一下父亲边放在哪个集合即可。
    分治下去即可,在这个过程中可以做到使两个集合的边数量尽量接近,注意到这个过程有 T ( m ) = T ( α m ) + T ( ( 1 − α ) m ) + Θ ( m ) T(m) = T(\alpha m) + T((1-\alpha) m) + \Theta(m) T(m)=T(αm)+T((1α)m)+Θ(m) α ∈ [ 1 3 , 2 3 ] \alpha \in \left[\frac 13, \frac 23\right] α[31,32]。复杂度为 Θ ( m log ⁡ m ) \Theta(m\log m) Θ(mlogm)

有一个 bonus,可以证明 C C C 为最大度数。

  1. C C C 不可能小于最大度数,这一点显然。
  2. 对度数进行归纳,假设当前二分图 G = ( L , R , E ) G=(L,R,E) G=(L,R,E) 最大度数为 C C C,下述 (3.) 将证明可以给出一个不相交边集 S ⊆ E S\subseteq E SE,使得 E \ S E\backslash S E\S 的最大度数为 C − 1 C - 1 C1。如能够给出,则归纳说明找到了一个 C C C 种颜色的解。
  3. 考虑将 L L L 中节点删去所有度数不为 C C C 的点以及其出边后得到 L ∗ L^* L,4. 中将证明我们一定能找到一组 L ∗ L^* L 的完美匹配。同理得到一组 R ∗ R^* R 的完美匹配,考虑两个边集的并,考虑将匹配看成一条有向边,每个点至多有一条出边,我们只需要保证删去一些边后每个原本有出边的点都仍然有出边或入边,因此如果有环则是偶环,隔一个删一个,然后将剩余的链的头部保留下一个删除,下一个保留……最后得到的显然是满足 2. 要求的一个边集。
  4. 考虑 L ∗ L^* L 的任一子集 S S S 以及其出边覆盖的对应点集合 T T T,记 d ( u ) d(u) d(u) 是原图中的度数, d ′ ( u ) d'(u) d(u) 是只考虑跨越 S , T S,T S,T 间边集上的边的情况下度数,可以得知 C ∣ S ∣ = ∑ u ∈ S d ( v ) = ∑ v ∈ T d ′ ( v ) ≤ ∑ v ∈ T d ( v ) ≤ C ∣ T ∣ C|S|=\sum_{u\in S} d(v)=\sum_{v\in T} d'(v) \le \sum_{v\in T}d(v)\le C|T| CS=uSd(v)=vTd(v)vTd(v)CT,因此 ∣ S ∣ ≤ ∣ T ∣ |S|\le |T| ST,由 Hall 定理,必然存在一组完美匹配。
#include <cassert>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <cctype>

#include <algorithm>
#include <tuple>
#include <random>
#include <bitset>
#include <chrono>
#include <queue>
#include <functional>
#include <set>
#include <map>
#include <vector>
#include <iostream>
#include <limits>
#include <numeric>

using namespace std;

struct E {
  int v, i;
  E* next;
};

const int M = 500010, N = 200010;

int a, b, m, c;
int u[M], v[M], id[M], ans[M];
int deg[N], stk[N], si[N];
bool curc[N];
tuple<int, int, int> dat[M];
bool vis[N], evs[M], col[M];
E pool[M * 2 + N];
E *g[N], *t[N], *pt;

void adde(E** gg, int u, int v, int i) {
  E* p = pt++;
  p->v = v;
  p->i = i;
  p->next = gg[u];
  gg[u] = p;
}

void dis(int uu) {
  if (vis[uu]) return;
  int s = 0;
  ++s; stk[s] = uu; vis[uu] = true; si[s] = -1;
  while (s) {
    int u = stk[s];
    if (si[s] != -1) {
      adde(t, u, g[u]->v, g[u]->i);
      ++deg[u];
      g[u] = g[u]->next;
    }
    si[s] = 1;
    while (g[u] && evs[g[u]->i]) g[u] = g[u]->next;
    if (!g[u]) {
      --s;
      continue;
    }
    int v = g[u]->v, i = g[u]->i;
    evs[i] = true;
    if (vis[v]) {
      bool f = false;
      while (stk[s] != v) {
        vis[stk[s]] = false;
        col[g[stk[s]]->i] = f;
        g[stk[s]] = g[stk[s]]->next;
        f = !f;
        --s;
      }
      col[g[v]->i] = f;
      g[v] = g[v]->next;
      si[s] = -1;
    } else {
      ++s;
      stk[s] = v;
      si[s] = -1;
      vis[v] = true;
    }
  }
  int ql = 0, qr = 0;
  stk[++qr] = uu;
  while (ql < qr) {
    int u = stk[++ql];
    E* p = t[u];
    int a = deg[u] / 2;
    bool f = curc[u];
    while (a--) {
      col[p->i] = f;
      curc[p->v] = f;
      stk[++qr] = p->v;
      p = p->next;
    }
    f = !f;
    while (p) {
      col[p->i] = f;
      curc[p->v] = f;
      stk[++qr] = p->v;
      p = p->next;
    }
  }
}

void solve(int l, int r) {
  bool f = false;
  pt = pool;
  for (int i = l; i <= r; ++i) {
    ++deg[u[i]]; ++deg[v[i]];
    f |= deg[u[i]] > 1 || deg[v[i]] > 1;
  }
  if (!f) {
    ++c;
    for (int i = l; i <= r; ++i) {
      deg[u[i]] = deg[v[i]] = 0;
      ans[id[i]] = c;
    }
    return;
  }
  for (int i = l; i <= r; ++i) {
    adde(g, u[i], v[i], i);
    adde(g, v[i], u[i], i);
    deg[u[i]] = deg[v[i]] = 0;
  }
  for (int i = l; i <= r; ++i) {
    dis(u[i]);
    dis(v[i]);
  }
  int cnt = count(evs + l, evs + r + 1, true);
  for (int i = l; i <= r; ++i) {
    deg[u[i]] = deg[v[i]] = 0;
    vis[u[i]] = vis[v[i]] = evs[i] = false;
    g[u[i]] = g[v[i]] = t[u[i]] = t[v[i]] = NULL;
  }
  int il = l, ir = r;
  for (int i = l; i <= r; ++i)
    dat[col[i] ? ir-- : il++] = make_tuple(id[i], u[i], v[i]);
  for (int i = l; i <= r; ++i)
    tie(id[i], u[i], v[i]) = dat[i];
  solve(l, il - 1);
  solve(ir + 1, r);
}

int main() {
  pt = pool;
  scanf("%d%d%d", &a, &b, &m);
  for (int i = 1; i <= m; ++i) {
    scanf("%d%d", &u[i], &v[i]);
    v[i] += a;
  }
  iota(id + 1, id + m + 1, 1);
  solve(1, m);
  printf("%d\n", c);
  for (int i = 1; i <= m; ++i)
    printf("%d\n", ans[i]);
  return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值