[CF] D. Compression - Educational Codeforces Round 59 (Rated for Div. 2)

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

题目大意

给出一个 $n \times n$ 的 $01$ 矩阵 $A$。规定 $B$ 为 $A$ 的一个 $x$ 压缩矩阵,如果 $x | n$ 以及 $A[i][j] = B[\lceil \frac{i}{x} \rceil][\lceil \frac{j}{x} \rceil]$($1$-indexed)。

$4 \le n \le 5200$,且 $4 | n$。

$A$ 按照每行每 $4$ 位压缩成一个十六进制数的方式,给出这个十六进制数串($0 \sim F$)。

简要题解

题目是 $1$ 开始的下标,如果是 $0$ 开始的下标,则我们应该把除法都替换为向下取整。以下均以向下取整讨论(形式简单一些)。

显然对于某个 $x$ 我们可以 $n ^ 2$ 的检查。但是如果只考虑能整除 $n$ 的 $x$ 则这样的 $x$ 有 $O(\sqrt n)$ 个,实际上并不能通过本题。比如 $5200 = 2^4 \times 5^2 \times 13$ 一共有 $5 \times 3 \times 2 = 30$ 个约数要判断。

void solve() {
  int n; cin >> n;
  string s;
  for (int i = 0; i < n; i++) {
    cin >> s;
    for (int j = 0, k = 0; k < n; j++, k += 4) {
      int dec = hex2dec[s[j]];
      for (int l = 0; l < 4; l++) {
        a[i][k + l] = ((dec >> l) & 1);
      }
    }
  }

  auto check = [&](int x) -> bool {
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < n; j++) {
        if (a[i][j] != a[i / x * x][j / x * x]) {  
          return false;
        }
      }
    }
    return true;
  };

  int ans = 1;
  vector<int> valid(n + 1, 1);
  for (int i = 2; i <= n; i++) {
    if ((n % i) || !valid[i]) continue;

    if (!check(i)) {
      for (int j = i; j <= n; j += i) {
        valid[j] = 0;
      }
    } else {
      cmax(ans, i);
    }
  }

  cout << ans << '\n';
}

实际上我们并不需要判断所有的因子。注意到如果对于 $gcd(x, y) = 1$ 如果 $x$ 是合法压缩,$y$ 是合法压缩,则 $xy$ 是合法压缩。

也就是要证明 $a[xy * k + i][xy * l + j]$ = $a[xy * k][xy * l], \forall 0 \le k, l < \frac{n}{xy}, 0 \le i, j < xy$。

注意到两维其实是可以分开的。不妨先证明对于

$a[x * j + i] = a[x * j], \forall 0 \le j < \frac{n}{x}, 1 \le i < x$ 和 $a[y * j + i], \forall 0 \le j < \frac{n}{y}, 1 \le i < y$,则 $a[xy * j + i] = a[xy * j], \forall 0 \le j < \frac{n}{xy}, 1 \le i < xy$。

我们从另一个角度考虑,这相当于对于 $x$ 只有 $i = x, 2x, 3x$ 的位置允许 $a[i] \neq a[i - 1]$。而 $y$ 只允许 $i = y, 2y, 3y$ 的位置 $a[i] \neq a[i - 1]$。由 $gcd(x, y) = 1$ 则只有 $i = k \cdot lcm(x, y) = kxy$ 的下标可能满足 $a[i] \neq a[i - 1]$ 即其满足 $xy$ 压缩。

这样我们只需判断 $n$ 的因子及其各个幂次,然后再合并即可,我们就得到了一个 $O(n ^ 2 \log n)$ 的做法。注意到质因子最多有 $12$ 个因此最多是 $12$ 次大概是刚才做法的 $1 / 3$。

坑点:

这题样例几乎等于没有。为了读入所以输入变成了这种形式,但是转化的时候要注意十六进制位与实际位置之间的对应关系(高位实际在左边较小的下标)。

复杂度

$T$:$O(n ^ 2 \log n)$:$812ms$ 时限是 $2.5s$。

$S$:$O(n ^ 2)$

代码实现

#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());
vector<int> isp, pri, minfac, maxfac;
void init_prime(int n) {
  isp.assign(n + 1, 1); isp[0] = isp[1] = 0;
  minfac.assign(n + 1, 0);
  maxfac.assign(n + 1, 0);
  for(int i = 2; i <= n; i++) {
    if(isp[i]) { pri.push_back(i); isp[i] = pri.size(); minfac[i] = i; }
    for(int p : pri) {
      if (i > n / p) break;
      isp[i * p] = 0;
      minfac[i * p] = p;
      if(i % p == 0) break;
    }
  }
  for (int i = 2; i <= n; i++) {
    maxfac[i] = max(maxfac[i / minfac[i]], minfac[i]);
  }
}
vector<int> getfac(int n) {
  vector<int> ans;
  while (n != 1) {
    int v = minfac[n];
    ans.push_back(v);
    n /= v;
  }
  return ans;
}
vector<pair<int, int>> getfacpii(int n) {
  vector<pair<int, int> > ans;
  while (n != 1) {
    int v = minfac[n];
    if (ans.empty() || ans.back().first != v) {
      ans.push_back({v, 1});
    } else {
      ans.back().second++;
    }
    n /= v;
  }
  return ans;
}
/*
---------1---------2---------3---------4---------5---------6---------7---------
1234567890123456789012345678901234567890123456789012345678901234567890123456789
*/

const int M = 5300;
int a[M][M];

map<char, int> hex2dec;
void init() {
  for (int i = 0; i < 9; i++) {
    hex2dec[i + '0'] = i;
  }
  for (int i = 0, j = 10; i < 6; i++, j++) {
    hex2dec[i + 'A'] = j;
  }
  init_prime(M);
}

void solve() {
  int n; cin >> n;
  string s;
  for (int i = 0; i < n; i++) {
    cin >> s;
    for (int j = 0, k = 0; k < n; j++, k += 4) {
      int dec = hex2dec[s[j]];
      for (int l = 0; l < 4; l++) {
        a[i][k + l] = ((dec >> (3 - l)) & 1);
      }
    }
  }

  auto check = [&](int x) -> bool {
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < n; j++) {
        if (a[i][j] != a[i / x * x][j / x * x]) {  
          return false;
        }
      }
    }
    return true;
  };

  int ans = 1;
  
  auto fac = getfacpii(n);
  for (auto [v, c] : fac) {
    int cur = 1;
    for (int i = 1; i <= c; i++) {
      if (check(cur * v)) {
        cur *= v;
      } else {
        break;
      }
    }
    ans *= cur;
  }

  cout << ans << '\n';
}

int main() {
  init();
  int t = 1; 
  // cin >> t;
  while (t--) {
    solve();
  }
  return 0;
}

官解做法

因为矩阵里只有 $01$,所以我们可以直接通过求和判断是否全是某种字符。这样再去算所有因子时有

$\sum \frac{n ^ 2}{i ^ 2}$ 而 $\sum \frac{1}{i ^ 2} = \frac{\pi}{6}$ 因此我们得到了一个 $O(n ^ 2)$ 的做法!

Prev: [CF] E. Vasya and Binary String - Educational Codeforces Round 59 (Rated for Div. 2)
Next: [CF] C. Brutality - Educational Codeforces Round 59 (Rated for Div. 2)