Problem:
E： たい焼きマスターと食べ盛り - Taiyaki-Master and Eater

物語

つたさんはたい焼きを作るのがとても上手い。えびちゃんはそんなつたさんが作るたい焼きが大好きだ。　2 人は毎週たい焼きを作って食べて幸せに過ごしているが、たい焼きを作っているそばで食べ盛りのえびちゃんにつまみ食いをされてしまうのがつたさんの最近の悩みだ。

学校祭でたい焼きを作ることになったつたさんは、いつものようにえびちゃんにつまみ食いされていたら売上が心配なので、自分が注目している範囲のたい焼きの様子を知ることができればいいなと思った。学校祭が成功するように、つたさんを助けてあげよう。

問題

縦 H、横 W の長方形状のたい焼きプレートがあり、たい焼きをセットしてから焼き上がるまでに T 分かかる。このプレートはたい焼きを一度に最大 HW 個焼くことができ、プレートの上から i (1 \leq i \leq H) 番目で左から j (1 \leq j \leq W) 番目の場所で焼いているたい焼きは (i, j) で表される。

次の 3 種類のイベントが合計 Q 回発生する。なお、初期状態ではたい焼きはいずれの場所にもセットされていない。

時刻 t につたさんが (h, w) にたい焼きを 1 つセットする。そのたい焼きは T 分で焼き上がり、時刻 t+T の時点で食べることができるようになる。ただし、すでにたい焼きがセットされている場所にたい焼きをセットすることはない。

時刻 t に、えびちゃんが (h, w) にあるたい焼きをつまみ食いしようとする。ただし、つまみ食いできるのはすでに焼き上がったたい焼きのみである。つまみ食いした後はその場所からたい焼きが無くなる (すなわち、たい焼きがセットされていない状態になる)。たい焼きが焼き上がっていない、またはたい焼きがない場所にもつまみ食いのイベントが発生する場合があることに注意せよ。

左上が (h_1, w_1)、右下が (h_2, w_2) となるような長方形領域内 ((h_1, w_1) および (h_2, w_2) を含む) のたい焼きについて、次の数が時刻 t の時点でそれぞれいくつであるかをつたさんがカウントする。

すでに焼き上がったたい焼きの数

まだ焼き上がっていないたい焼きの数

入力形式

H W T Q
t_1 c_1 h_{11} w_{11} (h_{12} w_{12})
t_2 c_2 h_{21} w_{21} (h_{22} w_{22})
…
t_Q c_Q h_{Q1} w_{Q1} (h_{Q2} w_{Q2})

入力は全て整数で与えられる。

1 行目には、たい焼きプレートの縦 H、 横 W、 たい焼きをセットしてから焼きあがるまでの時間 T、 イベントの数 Q が与えられる。

1+i (1 \leq i \leq Q) 行目には、時刻 t_i で発生したイベントの内容が与えられる。

c_i はイベントの種類を表す。この値が 0 ならばたい焼きをセットするイベント、 1 ならばつまみ食いをするイベント、 2 ならばたい焼きをカウントするイベントを指す。

h_{i1} (1 \leq h_{i1} \leq H)、w_{i1} (1 \leq w_{i1} \leq W) は、c_i = 0, 1 において次のような意味である。

c_i = 0 のとき、(h_{i1}, w_{i1}) の場所にたい焼きを1つセットする。

c_i = 1 のとき、(h_{i1}, w_{i1}) の場所に焼き上がった状態のたい焼きがあればつまみ食いをし、そうでなければ何もしない。

h_{i2}、w_{i2} は、c_i = 2 のときのみ与えられる。

c_i = 2 のとき、左上を (h_{i1}, w_{i1})、 右下を (h_{i2}, w_{i2}) とする長方形領域内のたい焼きについてカウントする。

入出力が非常に多くなることが予想されるため、入出力に遅い関数を使っている場合は注意してください。

制約

1 \leq H \leq 2 \times 10^3

1 \leq W \leq 2 \times 10^3

1 \leq T \leq 10^9

1 \leq Q \leq 10^5

1 \leq t_i \leq 10^9

i \lt j ならば、 t_i \lt t_j

0 \leq c_i \leq 2

1 \leq h_{i1} \leq h_{i2} \leq H

1 \leq w_{i1} \leq w_{i2} \leq W

出力形式

(c_i = 2 (1 \leq i \leq Q) の個数) を n とする。たい焼きのカウントの結果を、次の書き方に従ってイベントの発生時間が早い順に n 行に出力せよ。

a_1 b_1
a_2 b_2
…
a_n b_n

a_i は、領域内にある「焼き上がったたい焼きの数」である。

b_i は、領域内にある「まだ焼き上がっていないたい焼きの数」である。

末尾の改行を忘れないこと。

入力例1

3 3 3 6
1 0 1 1
2 2 1 1 2 2
3 1 1 1
4 0 2 1
5 2 1 1 2 2
6 2 2 1 3 3

出力例1

0 1
1 1
0 1

この入出力例では以下のイベントが発生している。

時刻 1 分に、(1, 1) にたい焼きをセットする。このたい焼きは時刻 4 分に焼き上がる。

時刻 2 分に、左上 (1, 1)、右下 (2, 2) の長方形領域にあるたい焼きをカウントする。(1, 1) にセットしたたい焼きはまだ焼き上がっていないため、0 1 を出力する。

時刻 3 分に、(1, 1) にあるたい焼きをつまみ食いしようとするが、まだ焼き上がっていないので何もしない。

時刻 4 分に、(2, 1) にたい焼きをセットする。このたい焼きは時刻 7 分に焼き上がる。

時刻 5 分に、左上 (1, 1)、右下 (2, 2) の長方形領域にあるたい焼きをカウントする。(1, 1) のたい焼きは焼き上がっており、 (2, 1) のたい焼きはまだ焼き上がっていないため、1 1 を出力する。

時刻 6 分に、左上 (2, 1)、右下 (3, 3) の長方形領域にあるたい焼きをカウントする。(2, 1) のたい焼きはまだ焼き上がっていないため、0 1 を出力する。

Solution in C++ (judge verdict: Accepted)

#include <stdio.h>
#include <cmath>
#include <algorithm>
#include <cfloat>
#include <stack>
#include <queue>
#include <vector>
#include <string>
#include <iostream>
#include <set>
#include <map>
#include <time.h>
typedef long long int ll;
typedef unsigned long long int ull;
#define BIG_NUM 2000000000
#define MOD 1000000007
#define EPS 0.000000001
using namespace std;


#define NUM 2010

enum Type{
	EXIST,
	BAKED,
};

enum Instruction{
	Eat,
	Count,
	Bake_start,
	Bake_complete,
};

struct Info{
	Info(){
		time = 0;
		instruction = Count;
		row_1 = 0;
		col_1 = 0;
		row_2 = 0;
		col_2 = 0;
		priority = 0;
	}

	Info(int arg_time,Instruction arg_instruction,int arg_row_1,
			int arg_col_1,int arg_row_2,int arg_col_2,int arg_priority){
		time = arg_time;
		instruction = arg_instruction;
		row_1 = arg_row_1;
		col_1 = arg_col_1;
		row_2 = arg_row_2;
		col_2 = arg_col_2;
		priority = arg_priority;
	}
	bool operator<(const struct Info &arg) const{
		if(time != arg.time){
			return time > arg.time; //時間の昇順(PQ)
		}else{
			return priority > arg.priority; //優先度の昇順(PQ)
		}
	}
	int time,row_1,col_1,row_2,col_2,priority;
	Instruction instruction;
};

int H,W,T,num_query;
int BIT[NUM][NUM][2];

void add(int add_row,int add_col,int value,Type type){
	for(int row = add_row; row <= H; row += row & -row){
		for(int col = add_col; col <= W; col += col & -col){
			BIT[row][col][type] += value;
		}
	}
}

int getSum(int get_row,int get_col,Type type){
	int ret = 0;
	for(int row = get_row; row > 0; row -= row & -row){
		for(int col = get_col; col > 0; col -= col & -col){
			ret += BIT[row][col][type];
		}
	}
	return ret;
}

int getSum(int from_row,int from_col,int to_row,int to_col,Type type){
	return getSum(to_row,to_col,type)-getSum(from_row-1,to_col,type)-getSum(to_row,from_col-1,type)+getSum(from_row-1,from_col-1,type);
}

int main(){

	scanf("%d %d %d %d",&H,&W,&T,&num_query);

	for(int row = 0; row <= H; row++){
		for(int col = 0; col <= W; col++){
			BIT[row][col][EXIST] = 0;
			BIT[row][col][BAKED] = 0;
		}
	}

	priority_queue<Info> Q;

	int tmp_time,command,row_1,col_1,row_2,col_2;

	//焼き上がりイベントを追加するので、いったん全てPQに突っ込む
	for(int loop = 0; loop < num_query; loop++){
		scanf("%d %d",&tmp_time,&command);
		switch(command){
		case 0:
			scanf("%d %d",&row_1,&col_1);
			Q.push(Info(tmp_time,Bake_start,row_1,col_1,-1,-1,0));
			Q.push(Info(tmp_time+T,Bake_complete,row_1,col_1,-1,-1,0));
			break;
		case 1:
			scanf("%d %d",&row_1,&col_1);
			Q.push(Info(tmp_time,Eat,row_1,col_1,-1,-1,1));
			break;
		case 2:
			scanf("%d %d %d %d",&row_1,&col_1,&row_2,&col_2);
			Q.push(Info(tmp_time,Count,row_1,col_1,row_2,col_2,1));
			break;
		}
	}

	int exist_num,baked_num;
	Info query;

	while(!Q.empty()){
		query = Q.top();
		Q.pop();

		switch(query.instruction){
		case Bake_start:
			add(query.row_1,query.col_1,1,EXIST); //存在テーブルに加算
			break;
		case Bake_complete:
			add(query.row_1,query.col_1,1,BAKED); //焼き上がりテーブルに加算
			break;
		case Eat:
			if(getSum(query.row_1,query.col_1,query.row_1,query.col_1,BAKED) == 1){ //焼きあがっている場合
				add(query.row_1,query.col_1,-1,BAKED); //焼き上がりテーブルから削除
				add(query.row_1,query.col_1,-1,EXIST); //存在テーブルから削除
			}
			break;
		case Count:
			exist_num = getSum(query.row_1,query.col_1,query.row_2,query.col_2,EXIST);
			baked_num = getSum(query.row_1,query.col_1,query.row_2,query.col_2,BAKED);
			printf("%d %d\n",baked_num,exist_num-baked_num);
			break;
		}
	}

	return 0;
}