公式

C - Drop Blocks 解説 by mechanicalpenciI


シミュレーションを用いてクエリを処理することを考えます。このとき、管理する必要のある情報は

  • マス \(i\) \((1\leq i\leq N)\) に積まれているブロックの数 \(A_i\)
  • \(j\)\((1\leq j\leq Q)\) 個以上のブロックが積まれているマスの数 \(B_j\)

です。しかし、これらを管理しようとすると、すべてのマスから \(1\) 個ずつブロックを削除するたびに \(B_j\) をすべて更新する必要があり、最大で \(\Theta(Q^2)\) 回更新する必要があり、時間制限が厳しいです。

その点に向けた工夫はいくつか考えられますが、一例としてはそもそも「すべてのマスから \(1\) 個ずつブロックを削除する」操作を行わない、ということが考えられます。このとき、元の問題における \(2\) 種類目のクエリにおいて出力すべき値は、削除しない場合における以下の値と等しいです。

各マスに積まれているブロックの個数の最小値 \(\displaystyle\min_{1\leq i\leq N} A_i\)\(k\)としたとき、\((y+k)\) 個以上のブロックが積まれているマスの個数 \(B_{y+k}\)

ここで、クエリごとに \(\displaystyle\min_{1\leq i\leq N} A_i\) を求めると最悪の場合、全体で \(O(NQ)\) の時間計算量がかかってしまいますが、この値は \(B_j=N\) である最大の \(j\) と言い換えることもできます。 この値は \(1\) 種類目のクエリのたびに高々 \(1\) 増加するかどうかであるため、この値をシミュレーションを通じて管理し続け、クエリのたびに更新されたかを確認することで全体で \(O(Q)\) の計算量で処理することができます。

以上を踏まえると、\((A_1,\ldots,A_N)\), \((B_1,\ldots,B_Q)\), \(k\) を保持して以下のようにクエリを処理することができます。

  • 最初の状態

    • \(A_i=0\)
    • \(B_j=0\)
    • \(k=0\)
  • \(1\) 種類目のクエリ

    • \(A_x\)\(1\) 増やす。
    • \(B_{A_x}\)\(1\) 増やす。
    • \(B_{A_x}=N\) ならば \(k=N\) に変更する。
  • \(2\) 種類目のクエリ

    • \(k+y>Q\) ならば \(0\), \(k+y\leq Q\) ならば \(B_{k+y}\) を出力する。

必要な計算量は \(O(N+Q)\) となり、十分高速です。よって、この問題を解くことができました。

C++ による実装例:

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

#define N (int)3e+5

int main(void){
	int a[N+1]={};
	int c[N+1]={};
	int n,q,t,x,mn=0;

	cin>>n>>q;
	for(int i=0;i<q;i++){
		cin>>t>>x;
		if(t==1){
			a[x]++;
			c[a[x]]++;
			if(c[a[x]]==n)mn=a[x];
		}
		if(t==2){
			if(x+mn>q)cout<<0<<endl;
			else cout<<c[x+mn]<<endl;
		}
	}

	return 0;
}

投稿日時:
最終更新: