fuu32のブログ

精進記録

CSA Round #82 Restricted Arrays

問題概要

CS Academy
長さNの数列AKが与えられる。以下の条件を満たす数列Bはいくつあるか求める。
条件1. max(B_i) is minimum
条件2. B_i  > 0
条件3.  | i - j | < K を満たす i,jについて、A_i=A_jならばB_i=B_j,A_i \neq A_jならばB_i \neq B_jが成り立つ。

制約

1 \leq K \leq N \leq 10^{5}
1 \leq A_i \leq N

考えたこと

まず条件1.なんですが、数列Bの最大値が最小のものになっているという感じだと考えられます(いい感じに訳せません)。2つ目のテストケースを見てみると、数列A=\{2,1,2,2\}では条件1.を無視すると以下のような候補が考えられます。

 \{1,2,1,1\} max(B_i)=2
 \{2,1,2,2\} max(B_i)=2
 \{2,3,2,2\} max(B_i)=3
 \{4,3,4,4\} max(B_i)=4

下2つも条件2,3を満たすがmax(B_i)の値がとることのできる最小値ではないので全ての条件を満たすのは上2つのみとなります。

問題文の理解はできましたが、なかなか考察は進みません。
こういう考えやすいケースから考え始めるのが良さそうです。 まず、K=1の時これは1つ目のテストケースですが、この場合条件3は考慮しなくていいので、全てに1を使うことで条件1,2を満たしmax(B_i)=1になります。

次にK=Nの時を考えてみます。この場合は条件3の与える範囲が数列A全体なので、数列Aに含まれる数字の種類の数だけ用いれば数列Bを構成できそうです。つまり max(B_i) = A_1からA_Nの範囲に現れる数字の種類 になります。

ではK=N-1の場合はどうでしょうか。この時、A_1からA_{N-1}の範囲とA_2からA_Nの範囲についてK=Nの時と同様に考えればいいと思います。それぞれの区間で必要な種類数のmaxをとれば全体としてのmax(B_i)が求まります。

なので与えられたKについて、幅K(N-K+1)個の区間に対してその区間に何種類の数字が使われているのかを調べるといいです。 実装では、幅K区間についてしゃくとりのように先頭から見ていくような感じにしました。

ここまでで、使える数字の種類(max(B_i))が求まったので、B_iに何通りの候補があるかを調べていきます。

B_1は自明にmax(B_i)通り。B_iに何通りあるを決める時、A_iとその直前のK-1個を見て、A_iと同じ数字が直前のK-1個の中にあるならばB_i=1、そうでないならばB_i= max(B_i)-直前K-1個にある数字の種類 となるはず。

全てのB_iを求め終わったら、 \prod_{i=1}^N B_i mod ( 10^{9}+7)を取りながら計算することで答えが出ます。

提出コード

#include <algorithm>
#include <bitset>
#include <cmath>
#include <cstring>
#include <deque>
#include <functional>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stdlib.h>
#include <string>
#include <utility>
#include <vector>
#define rep(i, n) for (int i = 0; i < (n); i++)
#define loop(i, x, n) for (int i = (x); i < (n); i++)
#define all(v) (v).begin(), (v).end()
#define int long long
using namespace std;
const int MOD = 1e9 + 7;
const int INF = 1e9;
template<typename T> void cmax(T &a, T b) { a = max(a, b); }
template<typename T> void cmin(T &a, T b) { a = min(a, b); }

signed main() {
  int n, k;
  cin >> n >> k;
  vector<int> a(n);
  rep(i, n) cin >> a[i];

  int maxB = 0;
  map<int, int> mp;
  rep(i, k) mp[a[i]]++;
  cmax(maxB, (int)mp.size());

  loop(i, k, n) {
    mp[a[i]]++;
    mp[a[i - k]]--;
    if (mp[a[i - k]] == 0) mp.erase(a[i - k]);
    cmax(maxB, (int)mp.size());
  }

  int ans = 1;
  map<int, int> m;
  vector<int> b(n);
  b[0] = maxB;
  m[a[0]] = 1;
  loop(i, 1, k) {
    if (m.count(a[i])) {
      m[a[i]]++;
      b[i] = 1;
    } else {
      b[i] = maxB - (int)m.size();
      m[a[i]]++;
    }
  }
  loop(i, k, n) {
    m[a[i - k]]--;
    if (m[a[i - k]] == 0) m.erase(a[i - k]);
    if (m.count(a[i])) {
      b[i] = 1;
    } else {
      b[i] = maxB - (int)m.size();
    }
    m[a[i]]++;
  }
  rep(i, n) {
    ans *= b[i];
    ans %= MOD;
  }
  cout << ans << endl;
  return 0;
}

感想

こういう問題は苦手ですが、あるケースを考えてその一つ小さい/大きいケースを考えていくのは有効なんだと感じる。言語化の能力が低いので、こんな説明で伝わるかが不安。