競技プログラミング日記

主に AtCoder の記事です

AtCoder Beginner Contest 146E問題

\(\def \set #1#2{\{ #1 \ \vert \ #2 \}}\)

ABC146E

解法

\(a\) の累積和を \(s\) とおく. \(a\) の空でない連続する区間を \([l,r)\) とおく. ans は,\(\ (l,r)\ \) の組であって

\(s[r] - s[r] % K = r-l\) and
\(0 \leq l < r \leq N\)

を満たすものの個数. 基本的には mod \(K\) で考えたいが,1つ目の式の右辺は mod \(K\) をとっていない事に注意. よって,1つ目の式は,

\(r-l < K\) and \(s[r] - s[l] \equiv r-l\) mod \(K\)

と同値.両辺を \(r,l\) の式に分離すると, \(s[r] - r \equiv s[l]-l\) mod \(K\).

実装

\(r-l < K\) の部分を一旦無視して \(s[r] - r \equiv s[l]-l\) mod \(K\) だけ考える.これは
\( for(r,N) \)
   \( ans\) += \(cnt[s[r]-r\) mod \(K]; \)
   \( cnt[s[r]-r\) mod \(K]\)++;
の形で求まる.
次に \(r-l < K\) の部分を考える. \(r-l < K\)を満たさない \(l\) に対して cnt[s[l]-l mod K]--; を施せばよい. これは,累積和の区間の幅や,queueを使って実装できる. Queue には,消す候補の map の key \(s[l]-l\) をストックしておく. \(K\) 個以上溜まったら削除に移る.

使っている記号,マクロ等 "https://ecsmtlir.hatenablog.com/entry/2022/12/23/131925"

int main() {
  ll n,k;
  cin >> n >> k;
  vll s(n+1); rep(i,n) cin >> s[i+1], s[i+1] += s[i];

  {// quque
    queue<ll> q;
    ll ans = 0;
    map<ll,ll> cnt;
    rep(r,n+1){
      while(q.size() >= k) cnt[q.front()]--, q.pop();

      ll x = (s[r] - r + n*k) % k;
      ans += cnt[x];

      cnt[x]++;
      q.push(x);
    }
    cout << ans << endl;
  }

  return 0;
}

// -------------------------------------
{ // 累積和の区間の幅を利用
  map<ll,ll> cnt;
  ll ans = 0;
  rep(r,n+1){
    ll l = r-k;
    if(l >= 0) cnt[(s[l]-l+n*k)%k]--;

    ll x = (s[r] - r + n*k) % k;
    ans += cnt[x];

    cnt[x]++;
  }
  cout << ans << endl;

}