CF #458 G. Sum the Fibonacci

AND, OR, XOR 畳み込みを使う。ANDは分割統治、ORは高速ゼータ変換と高速メビウス変換、XORは高速アダマール変換を使うことで処理できる。

n=2^17 としたとき、ANDがO(n log n)、ORがO(n log^2 n)、XORがO(n log n)になっている。OR も O(n log n) で行けるのだろうか?

#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <functional>
#include <ctime>

using namespace std;

const int mod = 1e9 + 7;

struct Modint {
  int n;
  Modint(int n = 0) : n(n) {}
};

Modint operator+(Modint a, Modint b) { return Modint((a.n += b.n) >= mod ? a.n - mod : a.n); }
Modint operator-(Modint a, Modint b) { return Modint((a.n -= b.n) < 0 ? a.n + mod : a.n); }
Modint operator*(Modint a, Modint b) { return Modint(1LL * a.n * b.n % mod); }
Modint &operator+=(Modint &a, Modint b) { return a = a + b; }
Modint &operator-=(Modint &a, Modint b) { return a = a - b; }
Modint &operator*=(Modint &a, Modint b) { return a = a * b; }

Modint modpow(Modint a, long long b) {
  Modint res = 1;
  while (b > 0) {
    if (b & 1) res *= a;
    a *= a;
    b >>= 1;
  }
  return res;
}

void fast_zeta_transform(vector<Modint> &f) {
  for (int i = 0; (1 << i) < f.size(); i++) {
    for (int j = 0; j < f.size(); j++) {
      if (j & 1 << i) {
        f[j] += f[j ^ 1 << i];
      }
    }
  }
}

void fast_mobius_transform(vector<Modint> &f) {
  for (int i = 0; (1 << i) < f.size(); i++) {
    for (int j = 0; j < f.size(); j++) {
      if (j & 1 << i) {
        f[j] -= f[j ^ 1 << i];
      }
    }
  }
}

void hadamard_transform(std::vector<Modint> &a, int l, int r) {
  if (r - l == 1) return;
  int n = (r - l) / 2;
  int m = (l + r) / 2;
  hadamard_transform(a, l, m);
  hadamard_transform(a, m, r);
  for (int i = 0; i < n; i++) {
    Modint x = a[l + i], y = a[m + i];
    a[l + i] = x + y;
    a[m + i] = x - y;
  }
}

vector<Modint> xor_convolution(vector<Modint> a) {
  hadamard_transform(a, 0, a.size());
  vector<Modint> res(a.size());
  for (int i = 0; i < a.size(); i++) {
    res[i] = a[i] * a[i];
  }
  hadamard_transform(res, 0, res.size());
  Modint inv = modpow(res.size(), mod - 2);
  for (int i = 0; i < res.size(); i++) {
    res[i] *= inv;
  }
  return res;
}

int bitcnt[1 << 17];

vector<Modint> or_convolution(vector<Modint> a) {
  vector<Modint> res(a.size());
  vector<vector<Modint>> cnt(18, vector<Modint>(a.size()));

  for (int i = 0; i < a.size(); i++) {
    cnt[bitcnt[i]][i] += a[i];
  }
  for (int i = 0; i < 18; i++) {
    fast_zeta_transform(cnt[i]);
  }

  for (int i = 0; i <= 17; i++) {
    vector<Modint> tmp(a.size());
    for (int j = 0; j <= i; j++) {
      int k = i - j;
      for (int l = 0; l < a.size(); l++) {
        tmp[l] += cnt[j][l] * cnt[k][l];
      }
    }
    fast_mobius_transform(tmp);
    for (int j = 0; j < a.size(); j++) {
      if (bitcnt[j] == i) {
        res[j] += tmp[j];
      }
    }
  }
  return res;
}

vector<Modint> and_convolution(vector<Modint> a, vector<Modint> b) {
  function<void(int, int)> f = [&](int l, int r) {
    const int n = r - l;
    if (n == 1) {
      a[l] *= b[l];
      return;
    }
    const int m = (l + r) / 2;
    for (int i = 0; i < n / 2; i++) {
      a[l + i] += a[m + i];
      b[l + i] += b[m + i];
    }
    f(l, m);
    f(m, r);
    for (int i = 0; i < n / 2; i++) {
      a[l + i] -= a[m + i];
    }
  };
  f(0, a.size());
  return a;
}

int main() {
  int n;
  cin >> n;

  for (int i = 1; i < 1 << 17; i++) {
    bitcnt[i] = bitcnt[i & i - 1] + 1;
  }

  vector<Modint> f(1 << 17);
  for (int i = 0; i < n; i++) {
    int s;
    scanf("%d", &s);
    f[s] += 1;
  }

  vector<Modint> fib(1 << 17);
  fib[0] = 0;
  fib[1] = 1;
  for (int i = 2; i < 1 << 17; i++) {
    fib[i] = fib[i - 1] + fib[i - 2];
  }

  auto a = or_convolution(f);
  auto b = xor_convolution(f);

  for (int i = 0; i < a.size(); i++) {
    a[i] = fib[i] * a[i];
    b[i] = fib[i] * b[i];
    f[i] = fib[i] * f[i];
  }
  a = and_convolution(a, b);
  a = and_convolution(a, f);

  Modint ans;
  for (int i = 0; i < 17; i++) {
    ans += a[1 << i];
  }
  cout << ans.n << endl;
}

CF #457 E. Jamie and Tree

http://codeforces.com/problemset/problem/916/E

LC-tree は根を変更するクエリと lca を求めるクエリを捌ける。ET-tree は辺の追加・削除と連結成分全体に x を一様加算するというクエリが捌ける。これらを組み合わせることで容易に解くことができる。

#include <iostream>
#include <algorithm>
#include <vector>
#include <map>

using namespace std;

struct ETTree {
  struct node {
    node *l = nullptr;
    node *r = nullptr;
    node *p = nullptr;
    bool active;
    int size;
    long long val = 0;
    long long lazy = 0;
    long long sum = 0;

    node(bool active_, int v = 0) : active(active_), size(active_), sum(v), val(v) {}
  };

  map<pair<int, int>, node *> edges;
  vector<node *> super;
  const int n;

  ETTree(const vector<vector<int>> &g, vector<int> a) : n(g.size()) {
    super.resize(n);
    for (int i = 0; i < n; i++) {
      for (int j : g[i]) {
        edges[{i, j}] = new node(false);
      }
      super[i] = new node(true, a[i]);
    }
    for (int i = 0; i < n; i++) {
      for (int j : g[i]) if (i < j) {
        link(i, j);
      }
    }
  }

  void link(int u, int v) {
    node *uv = edges[{u, v}];
    node *vu = edges[{v, u}];
    node *x = super[u];
    node *y = super[v];
    rotate(x);
    rotate(y);
    join(x, uv);
    join(uv, y);
    join(y, vu);
  }

  void cut(int u, int v) {
    node *uv = edges[{u, v}];
    node *vu = edges[{v, u}];
    rotate(uv);
    split_right(uv);
    split_left(vu);
    split_right(vu);
  }

  void add(int u, long long w) {
    put_lazy(splay(super[u]), w);
  }

  int find_size(int u) {
    return splay(super[u])->size;
  }

  long long access(int u) {
    splay(super[u]);
    return splay(super[u])->sum;
  }

  void rot(node *x) {
    node *y = x->p, *z = y->p;
    if (z) {
      if (y == z->l) z->l = x;
      if (y == z->r) z->r = x;
    }
    x->p = z; y->p = x;
    if (x == y->l) {
      y->l = x->r; x->r = y;
      if (y->l) y->l->p = y;
    } else {
      y->r = x->l; x->l = y;
      if (y->r) y->r->p = y;
    }
    pull(y);
  }

  void push_above(node *x) {
    if (x->p) push_above(x->p);
    push(x);
  }

  node *splay(node *x) {
    push_above(x);
    while (x->p) {
      node *y = x->p, *z = y->p;
      if (z) rot((x == y->l) == (y == z->l) ? y : x);
      rot(x);
    }
    pull(x);
    return x;
  }

  void pull(node *x) {
    x->size = x->active;
    if (x->l) x->size += x->l->size;
    if (x->r) x->size += x->r->size;
    // assert(x->lazy == 0);
    x->sum = 0;
    if (x->active) {
      x->sum += x->val;
    }
    if (x->l) x->sum += x->l->sum;
    if (x->r) x->sum += x->r->sum;
  }

  void put_lazy(node *x, long long v) {
    x->lazy += v;
    if (x->active) {
      x->val += v;
    }
    x->sum += v * x->size;
  }

  void push(node *x) {
    if (x->l) put_lazy(x->l, x->lazy);
    if (x->r) put_lazy(x->r, x->lazy);
    x->lazy = 0;
  }

  // [_ _ x _ _] + [_ _ y _ _] -> [_ _ x _ _ _ _ y _ _]
  void join(node *x, node *y) {
    if (!x || !y) return;
    splay(x);
    while (x->r) x = x->r;
    splay(x);
    splay(y);
    // assert(x->lazy == 0);
    x->r = y;
    y->p = x;
    pull(x);
  }

  // [_ _ x _ _] -> [_ _] [x _ _]
  node *split_left(node *x) {
    splay(x);
    // assert(x->lazy == 0);
    node *y = x->l;
    if (y) {
      y->p = x->l = nullptr;
      pull(x);
    }
    return y;
  }

  // [_ _ x _ _] -> [_ _ x] [_ _]
  node *split_right(node *x) {
    splay(x);
    // assert(x->lazy == 0);
    node *y = x->r;
    if (y) {
      y->p = x->r = nullptr;
      pull(x);
    }
    return y;
  }

  // [0 1 2 x 3 4 5] -> [x 3 4 5 0 1 2]
  void rotate(node *x) {
    join(x, split_left(x));
  }
};

struct node {
  node *l = nullptr;
  node *r = nullptr;
  node *p = nullptr;
  bool rev = false;
};

bool is_root(node *x) {
  return !x->p || (x != x->p->l && x != x->p->r);
}

void rot(node *x) {
  node *y = x->p, *z = y->p;
  if (z) {
    if (y == z->l) z->l = x;
    if (y == z->r) z->r = x;
  }
  x->p = z; y->p = x;
  if (x == y->l) {
    y->l = x->r; x->r = y;
    if (y->l) y->l->p = y;
  } else {
    y->r = x->l;
    x->l = y;
    if (y->r) y->r->p = y;
  }
}

void reverse(node *x) {
  swap(x->l, x->r);
  x->rev ^= true;
}

void push(node *x) {
  if (x->rev) {
    if (x->l) reverse(x->l);
    if (x->r) reverse(x->r);
  }
  x->rev = false;
}

void push_above(node *x) {
  if (x->p) push_above(x->p);
  push(x);
}

void splay(node *x) {
  while (!is_root(x)) {
    node *y = x->p;
    if (!is_root(y)) {
      node *z = y->p;
      rot((x == y->l) == (y == z->l) ? y : x);
    }
    rot(x);
  }
}

void splayLC(node *x) {
  node *tmp = x;
  push_above(x);
  for (node *r = nullptr; x; x = x->p) {
    splay(x);
    x->r = r;
    r = x;
  }
  splay(tmp);
}

void reroot(node *x) {
  splayLC(x);
  reverse(x);
}

void link(node *c, node *p) {
  reroot(c);
  splayLC(p);
  p->r = c;
  c->p = p;
}

void cut(node *x) {
  splayLC(x);
  x->l->p = nullptr;
  x->l = nullptr;
}

node *lca(node *x, node *y) {
  splayLC(y);
  splayLC(x);
  bool same = false;
  node *l = y;
  while (y != nullptr) {
    if (is_root(y) && y->p) {
      l = y->p;
    }
    if (y == x->r) return x;
    if (x == y) same = true;
    y = y->p;
  }
  if (!same) return nullptr;
  return l;
}

node *get_parent(node *x) {
  splayLC(x);
  if (!x->l) {
    return nullptr;
  }
  x = x->l;
  while (x->r) {
    x = x->r;
    push(x);
  }
  splayLC(x);
  return x;
}

int input() {
  int n;
  scanf("%d", &n);
  return n;
}

int main() {
  int n, q;
  cin >> n;
  cin >> q;
  vector<node *> lc(n);
  map<node *, int> mp;
  mp[nullptr] = -1;
  vector<vector<int>> g(n);
  for (int i = 0; i < n; i++) {
    lc[i] = new node();
    mp[lc[i]] = i;
  }
  vector<int> a(n);
  for (int i = 0; i < n; i++) {
    a[i] = input();
  }
  for (int i = 0; i < n - 1; i++) {
    int u = input() - 1;
    int v = input() - 1;
    g[u].push_back(v);
    g[v].push_back(u);
    link(lc[u], lc[v]);
  }
  reroot(lc[0]);

  ETTree et(g, a);
  while (q--) {
    int type = input();
    if (type == 1) {
      int v = input() - 1;
      reroot(lc[v]);
    } else if (type == 2) {
      int u = input() - 1;
      int v = input() - 1;
      int x = input();

      int l = mp[lca(lc[u], lc[v])];
      int p = mp[get_parent(lc[l])];

      if (p != -1) et.cut(l, p);
      et.add(l, x);
      if (p != -1) et.link(l, p);
    } else {
      int v = input() - 1;
      int p = mp[get_parent(lc[v])];
      if (p != -1) et.cut(v, p);
      long long ans = et.access(v);
      if (p != -1) et.link(v, p);
      printf("%lld\n", ans);
    }
  }
}

恐らく普通の解。

#include <iostream>
#include <algorithm>
#include <vector>
#include <map>
#include <functional>

using namespace std;

int input() {
  int n;
  scanf("%d", &n);
  return n;
}

struct HLD {
  using Tree = std::vector<std::vector<int>>;
  std::vector<int> parent, head, vid, inv;

  HLD(const Tree &g) : parent(g.size()), head(g.size()), vid(g.size()), inv(g.size()) {
    int k = 0;
    std::vector<int> size(g.size(), 1);
    std::function<void(int, int)> dfs = [&](int u, int p) {
      for (int v : g[u]) if (v != p) dfs(v, u), size[u] += size[v];
    };
    std::function<void(int, int, int)> dfs2 = [&](int u, int p, int h) {
      parent[u] = p; head[u] = h; vid[u] = k++; inv[vid[u]] = u;
      for (int v : g[u]) if (v != p && size[u] < size[v] * 2) dfs2(v, u, h);
      for (int v : g[u]) if (v != p && size[u] >= size[v] * 2) dfs2(v, u, v);
    };
    dfs(0, -1);
    dfs2(0, -1, 0);
  }

  int lca(int a, int b, int c) {
    while (true) {
      if (vid[a] < vid[b]) swap(a, b);
      if (vid[a] < vid[c]) swap(a, c);
      if (vid[b] < vid[c]) swap(b, c);
      if (head[a] == head[b]) return b;
      a = parent[head[a]];
    }
  }

  int find_parent(int u, int r) {
    if (u == r) return -1;
    while (vid[u] < vid[r]) {
      if (head[u] == head[r]) return inv[vid[u] + 1];
      if (parent[head[r]] == u) return head[r];
      r = parent[head[r]];
    }
    return parent[u];
  }
};

struct EulerTour {
  std::vector<int> l;
  std::vector<int> r;

  EulerTour(const std::vector<std::vector<int>> &g) {
    const int n = g.size();
    l.resize(n);
    r.resize(n);
    int k = 0;
    std::function<void(int, int)> dfs = [&](int u, int p) {
      l[u] = k++;
      for (int v : g[u]) if (v != p) dfs(v, u);
      r[u] = k;
    };
    dfs(0, -1);
  }
};

template<class T>
struct BIT {
  vector<T> s0;
  vector<T> s1;

  BIT(int n) : s0(n + 2), s1(n + 2) {}
  
  void add0(int k, T v) {
    for (int i = k + 1; i < s0.size(); i += i & -i) {
      s0[i] -= v * k;
      s1[i] += v;
    }
  }

  void add(int l, int r, T v) {
    add0(l, v);
    add0(r, -v);
  }

  T sum0(int k) {
    T t0 = 0;
    T t1 = 0;
    for (int i = k + 1; i > 0; i -= i & -i) {
      t0 += s0[i];
      t1 += s1[i];
    }
    return t0 + t1 * k;
  }

  T sum(int l, int r) {
    return sum0(r) - sum0(l);
  }

  T sumall() {
    return sum0(s0.size() - 2);
  }
};

int main() {
  int n, q;
  cin >> n;
  cin >> q;
  vector<vector<int>> g(n);
  vector<int> a(n);
  for (int i = 0; i < n; i++) {
    a[i] = input();
  }
  for (int i = 0; i < n - 1; i++) {
    int u = input() - 1;
    int v = input() - 1;
    g[u].push_back(v);
    g[v].push_back(u);
  }
  EulerTour et(g);
  HLD hld(g);

  int root = 0;
  long long all = 0;

  BIT<long long> bit(n);
  for (int i = 0; i < n; i++) {
    bit.add(et.l[i], et.l[i] + 1, a[i]);
  }

  while (q--) {
    int type = input();
    if (type == 1) {
      int v = input() - 1;
      root = v;
    } else if (type == 2) {
      int u = input() - 1;
      int v = input() - 1;
      int x = input();

      int l = hld.lca(u, v, root);
      int p = hld.find_parent(l, root);

      if (p == -1) {
        all += x;
      } else if (p == hld.parent[l]) {
        bit.add(et.l[l], et.r[l], x);
      } else {
        bit.add(et.l[p], et.r[p], -x);
        all += x;
      }
    } else {
      int v = input() - 1;
      int p = hld.find_parent(v, root);

      long long ans = 0;
      if (p == -1) {
        ans = all * n + bit.sumall();
      } else if (p == hld.parent[v]) {
        ans = all * (et.r[v] - et.l[v]) + bit.sum(et.l[v], et.r[v]);
      } else {
        ans += all * n + bit.sumall();
        ans -= all * (et.r[p] - et.l[p]) + bit.sum(et.l[p], et.r[p]);
      }

      printf("%lld\n", ans);
    }
  }
}

感想

ET-tree が遅い。

CF #457 D. Jamie and To-do List

http://codeforces.com/problemset/problem/916/D

誤読さえしなければ解法は自明。永続multiset と永続配列があれば良い。永続multisetは使いまわせそうな形で再実装した。内部的には LLRBtree を用いている。
ポインタ型が64bitというのが厄介で、nodeにポインタ型を持たせるとMLEする。(CFは32bit環境らしい。なんでMLEしたんだ…?)

#include <iostream>
#include <algorithm>
#include <vector>
#include <map>
#include <string>

using namespace std;

template<class T> struct MSnode {
  int l = 0;
  int r = 0;
  int sz = 0;
  int cnt = 0;
  T key;
  bool c;
};

template<class T> class MultiSet {
  using node_t = int;
  static int curr;
  static vector<MSnode<T>> xs;

  node_t root = 0;

public:
  void insert(T key) {
    root = insert(root, key);
    xs[root].c = false;
  }

  void erase(T key) {
    root = erase(root, key);
    xs[root].c = false;
  }

  int countLT(T key) {
    return countLT(root, key);
  }

private:
  node_t fix(node_t x) {
    xs[x].sz = xs[xs[x].l].sz + xs[xs[x].r].sz + xs[x].cnt;
    return x;
  }

  node_t create() {
    if (xs.size() <= curr) {
      xs.resize(xs.size() * 2);
    }
    xs[curr].sz = 1;
    xs[curr].cnt = 1;
    xs[curr].c = true;
    return curr++;
  }

  node_t create(T key) {
    int x = create();
    xs[x].key = key;
    return x;
  }

  node_t clone(node_t x) {
    node_t y = create();
    xs[y] = xs[x];
    return y;
  }

  node_t create(node_t x, node_t l, node_t r, bool c) {
    x = clone(x);
    xs[x].l = l;
    xs[x].r = r;
    xs[x].c = c;
    return fix(x);
  }

  node_t create(node_t x, node_t l, node_t r) {
    x = clone(x);
    xs[x].l = l;
    xs[x].r = r;
    return fix(x);
  }

  node_t create(node_t x, bool c) {
    x = clone(x);
    xs[x].c = c;
    return x;
  }

  node_t insert(node_t x, T key) {
    if (!x) return create(key);
    if (key == xs[x].key) {
      x = clone(x);
      xs[x].cnt++;
      x = fix(x);
    } else if (key < xs[x].key) {
      x = create(x, insert(xs[x].l, key), xs[x].r);
    } else {
      x = create(x, xs[x].l, insert(xs[x].r, key));
    }
    if (xs[xs[x].r].c) {
      x = create(xs[x].r, create(x, xs[x].l, xs[xs[x].r].l, true), xs[xs[x].r].r, xs[x].c);
    }
    if (xs[xs[x].l].c && xs[xs[xs[x].l].l].c) {
      x = create(xs[x].l, create(xs[xs[x].l].l, false), create(x, xs[xs[x].l].r, xs[x].r, false), true);
    }
    return x;
  }

  node_t erase(node_t x, T key) {
    if (!x) return 0;
    if (key == xs[x].key) {
      x = clone(x);
      xs[x].cnt--;
      x = fix(x);
    } else if (key < xs[x].key) {
      x = create(x, erase(xs[x].l, key), xs[x].r);
    } else {
      x = create(x, xs[x].l, erase(xs[x].r, key));
    }
    return x;
  }

  int countLT(node_t x, T key) {
    if (!x) return 0;
    int res = 0;
    if (xs[x].key < key) {
      res += xs[xs[x].l].sz;
      res += xs[x].cnt;
      res += countLT(xs[x].r, key);
    } else {
      res += countLT(xs[x].l, key);
    }
    return res;
  }
};
template<class T> int MultiSet<T>::curr = 1;
template<class T> vector<MSnode<T>> MultiSet<T>::xs(1);

template<class T> class Array {
public:
  Array() {}

  Array(int n) {
    h = 0;
    for (int i = 1; i < n; i *= 16) h += 4;
  }

  T *mut_get(int k) {
    auto p = mutable_get(k, root, 0, h);
    root = p.first;
    return &p.second->value;
  }

  T get(int k) {
    return immutable_get(k, root, 0, h);
  }

private:
  struct node {
    node *ch[16] = {};
    T value;

    node() {}
    node(T value) : value(value) {}
  };

  int h;
  node *root = nullptr;

  T immutable_get(int a, node *x, int l, int d) {
    if (!x) return T();
    if (d == 0) return x->value;
    int id = a - l >> d - 4;
    return immutable_get(a, x->ch[id], l + (id << d - 4), d - 4);
  }

  pair<node *, node *> mutable_get(int a, node *x, int l, int d) {
    x = x ? new node(*x) : new node();
    if (d == 0) return { x, x };
    int id = a - l >> d - 4;
    auto p = mutable_get(a, x->ch[id], l + (id << d - 4), d - 4);
    x->ch[id] = p.first;
    return { x, p.second };
  }
};

int main() {
  int q;
  cin >> q;

  vector<pair<MultiSet<int>, Array<int>>> his(q + 1);
  MultiSet<int> st;
  Array<int> ar(q);
  his[0] = make_pair(st, ar);

  map<string, int> stoi;

  auto get_id = [&](string key) {
    if (!stoi.count(key)) {
      int t = stoi.size();
      stoi[key] = t;
    }
    return stoi[key];
  };

  for (int ii = 1; ii <= q; ii++) {
    char type[30];
    char key[30];

    scanf("%s", type);

    if (type[0] == 's') {
      int v;
      scanf("%s %d", key, &v);
      int k = get_id(key);
      int pv = ar.get(k);
      if (pv != 0) {
        st.erase(pv);
      }
      *ar.mut_get(k) = v;
      st.insert(v);
    } else if (type[0] == 'r') {
      scanf("%s", key);
      int k = get_id(key);
      int v = ar.get(k);
      if (v != 0) {
        st.erase(v);
        *ar.mut_get(k) = 0;
      }
    } else if (type[0] == 'q') {
      scanf("%s", key);
      int k = get_id(key);
      int v = ar.get(k);
      if (v != 0) {
        printf("%d\n", st.countLT(v));
      } else {
        printf("-1\n");
      }
      fflush(stdout);
    } else {
      int d;
      scanf("%d", &d);

      tie(st, ar) = his[ii - d - 1];
    }
    his[ii] = make_pair(st, ar);
  }
}

AGC 020 A..C問題

A問題

端に追いやられたら負けである。よって相手に近寄るように移動するのが最適行動になる。この戦略を実際にシミュレートすることで答えが得られる。

main = do
  [n, a, b] <- map read . words <$> getLine
  putStrLn $ turnA n a b

turnA :: Int -> Int -> Int -> String
turnA n a b
  | a + 1 /= b = turnB n (a + 1) b
  | a /= 1 = turnB n (a - 1) b
  | otherwise = "Borys"

turnB :: Int -> Int -> Int -> String
turnB n a b
  | b - 1 /= a = turnA n a (b - 1)
  | b /= n = turnA n a (b + 1)
  | otherwise = "Alice"

B問題

$i$ 番目のラウンドは関数 $f_i(x)=\lfloor x/a_i \rfloor a_i$ で表せる。初期人数を $x$ としたとき $f_{K - 1} \circ f_{K - 2} \circ \cdots \circ f_0 (x)=2$ となる $x$ の最大値・最小値を求めれば良い。$f_i$ は単調増加関数なので、合成しても単調増加関数である。よって、$x$が取りうる範囲は二分探索により求めることができる。

import Data.List (foldl')

main = do
  _ <- getLine
  a <- map read . words <$> getLine

  let l = binarySearch ((>=2) . foo a) 0 (10^18)
  let r = binarySearch ((>=3) . foo a) 0 (10^18)

  if l < r then putStrLn $ show l ++ " " ++ show (r - 1)
           else print (-1)

foo :: [Int] -> Int -> Int
foo a x = foldl' (\a b -> a `div` b * b) x a

binarySearch :: (Int -> Bool) -> Int -> Int -> Int
binarySearch f l r
  | r - l == 1 = r
  | otherwise = let m = div (l + r) 2
                in if f m then binarySearch f l m
                          else binarySearch f m r

C問題

空集合も考慮する。サンプル1で作れる値は {0,1,1,2,2,3,3,4} である。$i$ 番目の値と $2^n-1-i$ 番目の値の和が一定であることは、互いに補集合の関係になっていることから言える。この対称性から、中央値は S を unique して中央値を取ったものと一致する。もう少し考えると、数列 $a$ の総和を $s$ としたとき $\lceil s/2 \rceil$ となる値が中央値であることが言える。

C++ であれば bitset を用いた $O(n^2\max a/64)$ の解法で十分間に合う。多倍長整数が使える言語であれば、多倍長整数の shift, or は高速であることが多いため間に合う。

import Data.List (foldl', find)
import Data.Bits (shiftL, (.|.), testBit)

main = do
  _ <- getLine  
  a <- map read . words <$> getLine
  let f = foldl' (\a x -> a .|. shiftL a x) 1 a :: Integer
  let s = sum a

  let (Just ans) = find (\x -> testBit f x) [(s + 1) `div` 2 ..]
  print ans
input()
a = list(map(int, input().split()))

dp = 1
for x in a:
  dp |= dp << x

s = sum(a)
dp >>= (s + 1) // 2
dp <<= (s + 1) // 2
dp = bin(dp)

print(len(dp) - len(dp.rstrip('0')))

感想

C++ の bitset 解法より Haskell の Integer 解法の方が速かった。計算範囲が若干減るからからかな。ところで bitset が想定だということに驚いている人が思ったより多いことに驚いている。

CF Good Bye 2017 G: New Year and Original Order

http://codeforces.com/contest/908/problem/G

解法

上の桁から決めていく桁 DP により解くことができる。ソート済み整数はレピュニット数*1 の和で表せるという性質が役に立つ。たとえば 1122244 という整数の場合

1122244
1111111
  11111
     11
     11

という表現が可能である。$\underbrace{1\dots1}_n=(10^{n+1}-1)/9$ と表せるため、以下の表現も可能である。

$$1122244\times 9 + 9 = 10^8 + 10^6 + 10^3 + 10^3 + 10^0 + 10^0 + 10^0 + 10^0 + 10^0$$

1122244 の次に 2 を使うとしよう。このとき次のような変化を見せる。

\begin{align}
1122244\times 9 + 9 &= \underline{10^8 + 10^6} + 10^3 + 10^0 + 10^0 + 10^0 + 10^0 + 10^0 + 10^0 \\
11222244\times 9 + 9 &= \underline{10^9 + 10^7} + 10^3 + 10^0 + 10^0 + 10^0 + 10^0 + 10^0 + 10^0
\end{align}

つまり 1 と 2 に対応するレピュニット数が 10 倍されて、それ以外はそのままになっている。ここまでわかると、1,2,3,...,9 に対応するレピュニット数の総和は独立に数え上げられることが分かる。よって、次のようなDPが可能となる。

\begin{align}
1122244\times 9 + 9 &= \underbrace{10^8}_{dp[i][less][1]} + \underbrace{10^6}_{dp[i][less][2]} + \underbrace{10^3}_{dp[i][less][3]} + 10^0 + 10^0 + 10^0 + 10^0 + 10^0 + 10^0 \\
11222244\times 9 + 9 &= \underbrace{10^9}_{dp[i+1][less][1]} + \underbrace{10^7}_{dp[i+1][less][2]} + \underbrace{10^3}_{dp[i+1][less][3]} + 10^0 + 10^0 + 10^0 + 10^0 + 10^0 + 10^0
\end{align}

\begin{align}
dp[i+1][j'][k] \gets \begin{cases}
10\times dp[i][j][k] & \text{$d \le k$} \\
dp[i][j][k] & \text{otherwise}
\end{cases}
\end{align}

ここで $d$ は遷移時に選んだ数字である。

Submission #33803662 - Codeforces

感想

アプローチの掛け方が少ないから少し考えれば解けるような問題だった。
レピュニット数は AtCoder でも最近見たしね。Eの方が圧倒的に難しい。