[CF] D. Cubes - Codeforces Round 295 (Div. 2)

https://codeforces.com/contest/520/problem/D

题目大意

给出 $n$ 个立方体(其实可能叫正方形更好,因为其实只需要考虑 $2$ 维),考虑 $OX$ 是地面,$OY$ 是向上的方向。每个立方体用 $(x, y)$ 表示其位置。定义某个立方体是稳定的当且仅当:

  1. 立方体在地面上($x = 0$)
  2. 其下方有某个稳定的立方体且它们有共用的边或角($(x - 1, y - 1), (x, y - 1), (x + 1, y)$ 中至少一个位置有立方体且其稳定)。

A 和 B 两个人轮流从中取出某个立方体,使得抽出后图形还是稳定的。将取出的立方体的下标按照取出的顺序排列,A 先取,并且希望这个 $n$ 进制数尽量大。B 希望数尽量小,问最后数字是多少,结果对 $10^9 + 9$ 取模。

$2 \le n \le 10^5$。$-10^9 \le x_i \le 10^9$。$0 \le y \le 10^9$。最初给出的图形是稳定的。

简要题解

容易想到我们可以一直维护当前可以取的集合,每次取完更新这个集合。A 肯定取当前能取最大的,B 反之,因为是个 $n$ 进制数,且最后序列是个排列,因此对于 A 不存在前面取的小,能更好的情况,对 B 同理。

静态的检查一个块是不是能移出也是容易的,因为只需检查它上面三个位置是否有块,如果有其是否有其他支撑块即可。当我们抽出一个块后,那么其下面的有些块可能因为不需要支撑了,也变得可以抽出了。特别的,其上面的某些块,可能因为这个支撑没了使得其他支撑块变得不可选,比较简单的写法就是直接检查两边上下周围的所有可能有影响的块。(我们注意到其实稍微远一点的块其检查的范围内完全没有变化,因此我们并不需要重新检查)。

注意这个检查范围要比抽出的 $(x, y)$ 紧邻的一圈大一些,因为有下面这样的情况。

    --
   |1 |
 -- -- --
|2 |  |0 |
 --    --

最初 $1$ 或 $2$ 都可以被抽出,但 A 抽走 $2$ 后会使得 $0$ 不能在 $1$ 之前抽出。

Tag 加了 BFS 但其实这不是什么正经的 BFS,只是写法有点像罢了。

注意模数。

复杂度

$T$:$O(n \log n)$

$S$:$O(n)$

代码实现

#include <bits/stdc++.h>
using namespace std;

int io_=[](){ ios::sync_with_stdio(false); cin.tie(nullptr); return 0; }();

using LL = long long;
using ULL = unsigned long long;
using LD = long double;
using PII = pair<int, int>;
using VI = vector<int>;
using MII = map<int, int>;

template<typename T> void cmin(T &x,const T &y) { if(y<x) x=y; }
template<typename T> void cmax(T &x,const T &y) { if(x<y) x=y; }
template<typename T> bool ckmin(T &x,const T &y) { 
    return y<x ? (x=y, true) : false; }
template<typename T> bool ckmax(T &x,const T &y) { 
    return x<y ? (x=y, true) : false; }
template<typename T> void cmin(T &x,T &y,const T &z) {// x<=y<=z 
    if(z<x) { y=x; x=z; } else if(z<y) y=z; }
template<typename T> void cmax(T &x,T &y,const T &z) {// x>=y>=z
    if(x<z) { y=x; x=z; } else if(y<z) y=z; }

// mt19937 rnd(chrono::system_clock::now().time_since_epoch().count());
// mt19937_64 rnd_64(chrono::system_clock::now().time_since_epoch().count());
template<long long Mo=998244353> struct ModInt {
  static long long MO;
  static void setMo(long long mo) { MO = mo; }
  long long x;
  ModInt(long long x=0) : x(x){ norm(); }
  friend istream &operator>>(istream& in, ModInt &B) { in>>B.x; return in; }
  friend ostream &operator<<(ostream& out, const ModInt &B) { 
    out<<B.x; return out; }
  // ModInt operator=(int x_) { x=x_; norm(); return *this; }
  void norm() { x = (x%MO + MO) % MO; }
  long long get() { return x; }

  ModInt operator-() const { return ModInt(MO - x); }
  ModInt operator+=(const ModInt &B) { x+=B.x; if(x>=MO) x-=MO; return *this; }
  ModInt operator-=(const ModInt &B) { x-=B.x; if(x<0) x+=MO; return *this; }
  ModInt operator*=(const ModInt &B) { x=x*B.x%MO; return *this; }
  ModInt operator+(const ModInt &B) const { ModInt ans=*this; return ans+=B; }
  ModInt operator-(const ModInt &B) const { ModInt ans=*this; return ans-=B; }
  ModInt operator*(const ModInt &B) const { ModInt ans=*this; return ans*=B; }
  ModInt operator^(long long n) const  {
    ModInt a=*this; ModInt ans(1);
    while(n) { if(n&1) ans*=a; a*=a; n>>=1; }
    return ans;
  }
  ModInt inv() const { return (*this)^(MO-2); } // if MO is prime
  ModInt operator/=(const ModInt &B) { (*this)*=B.inv(); return *this; }
  ModInt operator/(const ModInt &B) const { ModInt ans=*this; return ans/=B; }

  bool operator<(const ModInt &B) const { return x<B.x; }
  bool operator==(const ModInt &B) const { return x==B.x; }
  bool operator!=(const ModInt &B) const { return x!=B.x; }
};
template<long long Mo> long long ModInt<Mo>::MO = Mo;
// typedef ModInt<998244353> Mint;
// typedef ModInt<1'000'000'007> Mint;

/*
---------1---------2---------3---------4---------5---------6---------7---------
1234567890123456789012345678901234567890123456789012345678901234567890123456789
*/

typedef ModInt<1'000'000'009> Mint;

void solve() {
  int n; cin >> n;

  map<PII, int> pos2id;
  vector<PII> id2pos;
  int x, y;
  for (int i = 0; i < n; i++) {
    cin >> x >> y;
    pos2id[{x, y}] = i;
    id2pos.push_back({x, y});
  }
  
  auto check = [&](int x, int y) -> bool {
    for (int i = -1; i <= 1; i++) {
      if (!pos2id.count({x + i, y + 1})) continue;
      
      int f = 0;
      for (int j = -1; j <= 1; j++) {
        if (x + i + j == x) continue;

        if (pos2id.count({x + i + j, y})) {
          f = 1;
          break;
        }
      }

      if (!f) return false;
    }
    return true;
  };

  set<int> se;
  for (int i = 0; i < n; i++) {
    auto [x, y] = id2pos[i];
    if (check(x, y)) {
      se.insert(i);
    }
  }

  Mint ans = 0;
  for (int i = 0; i < n; i++) {
    // cerr << "se : ";
    // for (int i : se) cerr << i << ' ';
    // cerr << endl;

    int id;
    if (i & 1) {
      id = *se.begin();
    } else {
      id = *se.rbegin();
    }
    se.erase(id);
    
    auto [x, y] = id2pos[id];
    pos2id.erase({x, y});

    for (int i = -2; i <= 2; i++) {
      for (int j = -1; j <= 1; j++) {
        int nx = x + i, ny = y + j;
        if (auto it = pos2id.find({nx, ny}); it != pos2id.end()) {
          int id2 = it->second;
          if (check(nx, ny)) {
            se.insert(id2);
          } else {
            if (auto it2 = se.find(id2); it2 != se.end()) {
              se.erase(it2);
            }
          }
        }
      }
    }

    ans *= n;
    ans += id;
    
    // cerr << i << ' ' << id << ' ' << ans << endl;
  }
  cout << ans << '\n';
}

int main() {
  int t = 1; 
  // cin >> t;
  while (t--) {
    solve();
  }
  return 0;
}
Prev: [CF] H. Bro Thinks He's Him - Codeforces Round 1003 (Div. 4)
Next: [CF] B. Minimization - Codeforces Round 317 [AimFund Thanks-Round] (Div. 1)