CodeChef Replay of Indian IOI Training Camp (IOITC) #1 简要题解

CyclesAndColorings

先随便求出一棵生成树,去掉生成树的边之后:

  • 如果是二分图,那么原图是两个二分图组合起来的,假设第一个二分图中 i 的颜色是 c o l o r 1 [ i ] ,第二个二分图中 i 的颜色是 c o l o r 2 [ i ] ,那么在总的图中颜色变成 2 × c o l o r 1 [ i ] + c o l o r 2 [ i ] 即可(颜色下标从 0 开始)。

  • 如果不是二分图,则一定有一个奇环,因为生成树的边没有被删,所以删去后图仍然连通。

时间复杂度 O ( n + m )

#include <bits/stdc++.h>

using namespace std;

const int N = 200005;

int n, m, c[N], f[N], depth[N], parent[N];
vector<int> adj[N], tree[N];
bool visit[N];

int find(int x) {
  while (x != f[x]) {
    x = f[x] = f[f[x]];
  }
  return x;
}

void dfs(int x) {
  visit[x] = true;
  for (auto y : adj[x]) {
    if (!visit[y]) {
      depth[y] = depth[x] + 1;
      parent[y] = x;
      dfs(y);
    }
  }
}

bool find_odd_circle() {
  for (int i = 1; i <= n; ++i) {
    visit[i] = false;
  }
  for (int i = 1; i <= n; ++i) {
    if (!visit[i]) {
      parent[i] = 0;
      depth[i] = 1;
      dfs(i);
    }
  }
  for (int x = 1; x <= n; ++x) {
    for (auto y : adj[x]) {
      if (!(depth[x] + depth[y] & 1)) {
        puts("2");
        if (depth[x] < depth[y]) {
          swap(x, y);
        }
        printf("%d", depth[x] - depth[y] + 1);
        for (int i = x; i != parent[y]; i = parent[i]) {
          printf(" %d", i);
        }
        putchar(10);
        return true;
      }
    }
  }
  return false;
}

void dfs_color(int x) {
  for (auto y : tree[x]) {
    if (!~c[y]) {
      c[y] = c[x] ^ 1;
      dfs_color(y);
    }
  }
}

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  int T;
  scanf("%d", &T);
  while (T--) {
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; ++i) {
      c[i] = -1;
      f[i] = i;
      adj[i].clear();
      tree[i].clear();
    }
    for (int i = 1; i <= m; ++i) {
      int x, y;
      scanf("%d %d", &x, &y);
      if (find(x) != find(y)) {
        f[find(x)] = find(y);
        tree[x].push_back(y);
        tree[y].push_back(x);
      } else {
        adj[x].push_back(y);
        adj[y].push_back(x);
      }
    }
    if (!find_odd_circle()) {
      for (int i = 1; i <= n; ++i) {
        if (!~c[i]) {
          c[i] = 0;
          dfs_color(i);
        }
      }
      puts("1");
      for (int i = 1; i <= n; ++i) {
        printf("%d%c", ((c[i] << 1) + (depth[i] & 1)) + 1, i == n ? '\n' : ' ');
      }
    }
  }
  return 0;
}

K Perfect Matchings

首先求出任意一组完美匹配 M ,通过找增广路,如果发现了一个偶环,那么就找到了一个新的匹配 M

考虑一条在 M 中但不在 M 中的边 ( u , v ) ,所有匹配可以分成有 ( u , v ) 的和没有 ( u , v ) 的,分别递归即可。

考虑计算复杂度,不难发现整个搜索过程是一棵完全二叉树的形式(即每个节点都有两个儿子),在每个节点需要花费 O ( m ) 的时间,而每个非叶节点都对应一个新的匹配,所以节点数是 O ( k ) 的。

时间复杂度 O ( m k )

#include <bits/stdc++.h>

using namespace std;

const int N = 105;

int n, m, start, remain, visit_t, visit[N], parent[N], match_l[N], match_r[N];
bool already, stay[N], in_stack[N], erased[N][N];
vector<int> adj_l[N], adj_r[N];

bool find_match(int x) {
  if (visit[x] == visit_t) {
    return false;
  }
  visit[x] = visit_t;
  for (auto y : adj_l[x]) {
    if (!match_r[y] || find_match(match_r[y])) {
      match_r[y] = x;
      match_l[x] = y;
      return true;
    }
  }
  return false;
}

void find_cycle(int x) {
  if (already) {
    return;
  }
  visit[x] = visit_t;
  in_stack[x] = true;
  int y = match_l[x];
  for (auto z : adj_r[y]) {
    if (already) {
      break;
    }
    if (!stay[z] && !erased[z][y] && z != match_r[y]) {
      parent[z] = y;
      if (visit[z] != visit_t) {
        find_cycle(z);
      } else if (in_stack[z]) {
        start = z;
        already = true;
        break;
      }
    }
  }
  in_stack[x] = false;
}

void dfs() {
  int match[N];
  for (int i = 1; i <= n; ++i) {
    match[i] = match_l[i];
  }
  ++visit_t;
  already = false;
  for (int i = 1; i <= n && !already; ++i) {
    if (visit[i] != visit_t && !stay[i]) {
      find_cycle(i);
    }
  }
  if (!already) {
    return;
  }
  int x = start;
  do {
    match[x] = parent[x];
    x = match_r[parent[x]];
  } while (x != start);
  stay[x] = true;
  dfs();
  stay[x] = false;
  if (!remain || !--remain) {
    return;
  }
  erased[x][match_l[x]] = true;
  for (int i = 1; i <= n; ++i) {
    swap(match[i], match_l[i]);
    match_r[match_l[i]] = i;
  }
  dfs();
  for (int i = 1; i <= n; ++i) {
    swap(match[i], match_l[i]);
    match_r[match_l[i]] = i;
  }
  erased[x][match_l[x]] = false;
}

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  scanf("%d %d %d", &n, &m, &remain);
  for (int i = 1; i <= m; ++i) {
    int x, y;
    scanf("%d %d", &x, &y);
    y -= n;
    adj_l[x].push_back(y);
    adj_r[y].push_back(x);
  }
  for (int i = 1; i <= n; ++i) {
    ++visit_t;
    if (!find_match(i)) {
      puts("No");
      return 0;
    }
  }
  if (!remain) {
    puts("Yes");
    return 0;
  }
  dfs();
  puts(remain ? "No" : "Yes");
  return 0;
}

Circular Intervals

考虑二分答案,如果没有环的限制那么贪心确定就行了,否则选择 L 1 正着扫一遍得到最小的 A n ,再从 A n 逆推回来最大的 A 1 ,不难发现这就是最优策略。

时间复杂度 O ( n log m )

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

bool check(const vector<pair<ll, ll>> &intervals, ll limit, ll circle) {
  int n = intervals.size();
  ll current = intervals[0].first;
  for (int i = 1; i < n; ++i) {
    if (current + limit > intervals[i].second) {
      return false;
    }
    current = max(intervals[i].first, current + limit);
  }
  ll right = current;
  for (int i = n - 2; ~i; --i) {
    current = min(intervals[i].second, current - limit);
  }
  return circle - right + current >= limit;
}

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  int n;
  ll m;
  scanf("%lld %d", &m, &n);
  vector<pair<ll, ll>> intervals(n);
  for (int i = 0; i < n; ++i) {
    scanf("%lld %lld", &intervals[i].first, &intervals[i].second);
  }
  ll l = 1, r = m, result = 0;
  while (l <= r) {
    ll mid = l + r >> 1;
    if (check(intervals, mid, m)) {
      result = mid;
      l = mid + 1;
    } else {
      r = mid - 1;
    }
  }
  printf("%lld\n", result);
  return 0;
}

猜你喜欢

转载自blog.csdn.net/wxh010910/article/details/80834798