ARC082 F : Sandglass(700)

作成日:2017.09.03
最終更新日:2017.09.03

問題概要

問題本文

 パーツAとパーツBからなる砂時計に砂が$X$グラム入っている。 砂時計は一方のパーツが上、もう一方のパーツが下になるように置かれ、$1$秒間に$1$グラムの砂が上のパーツから砂がなくなるまで下のパーツに落ちる。 砂時計は時刻$0$にパーツAが上にあり、時刻$r_i$に瞬間的にひっくり返す。 $Q$個のクエリ「時刻$0$にパーツAに$a_i$グラムの砂が入っていた時、時刻$t_i$にパーツAに入っている砂の量を求めよ」に答えよ。
制約
$1 \leq X \leq 10^9$
$1 \leq K \leq 10^5$
$1 \leq r_1 < r_2 < ... < r_K \leq 10^9$
$1 \leq Q \leq 10^5$
$0 \leq t_1 < t_2 < ... < t_Q \leq 10^9$
$0 \leq a_i \leq X$
入力はすべて整数

考え方

 愚直に求めると計算量は$O(KQ)$となり間に合わないので工夫する。 そのために$t_i$よりも変化させた状態が考えにくい$a_i$をまとめて考えることにする。
 パーツAに入っている砂は時刻$r_1$まで減少するが、途中で空になった場合は減少しなくなる。 途中で空になった場合はその後は同じようにふるまうため、まとめて考えることができ、そうなるのは$a_i \leq r_1$の時である。 途中で空にならなかった場合は、残りの砂の量は異なるが減少した砂の量は等しい。 よってこの時もまとめて考えることができる。
 時刻$r_1$から時刻$r_2$までの場合も同様に考えると、パーツBが途中で空になるか否かでそれぞれまとめて考えられる。 時刻$r_2$以降も同様に考えれば、パーツAが途中で空になった場合、パーツBが途中で空になった場合、いずれでもない場合の3つにまとめることができる。 また、まとめる$a_i$の範囲は区間で表されるので、時刻$r_i$ごとに境界を保持していれば良い。 時系列順にその区間を更新しながらクエリに答えれば計算量は$O(K+Q)$となり十分高速である。

ソースコード

マクロ等はこちら

const int MC = 100010;
int X,K,Q;
int r[MC];
int t[MC] , a[MC];
int amin,amax;
int ansmin,ansmax;
int ans[MC];

int main(){
	scanf("%d%d" , &X , &K);
	repp(i,1,K+1){
		scanf("%d" , r + i);
	}
	scanf("%d" , &Q);
	repp(i,0,Q){
		scanf("%d%d" , t + i , a + i);
	}
	amin = ansmin = 0;
	amax = ansmax = X;
	int pt = 0;
	int sum = 0;
	repp(i,0,K){
		int z = (i%2?1:-1);
		while(pt < Q && t[pt] < r[i+1]){
			if(a[pt] < amin) ans[pt] = max(0,min(X,ansmin+z*(t[pt]-r[i])));
			else if(a[pt] > amax) ans[pt] = max(0,min(X,ansmax+z*(t[pt]-r[i])));
			else ans[pt] = max(0,min(X,a[pt]+sum+z*(t[pt]-r[i])));
			++pt;
		}
		sum += z*(r[i+1]-r[i]);
		if(sum > X) sum = X;
		if(sum < -X) sum = -X;
		amin = max(amin,min(X,-sum));
		amax = min(amax,max(0,X-sum));
		ansmin = max(0,min(X,ansmin+z*(r[i+1]-r[i])));
		ansmax = max(0,min(X,ansmax+z*(r[i+1]-r[i])));
	}
	int z = (K%2?1:-1);
	while(pt < Q){
		if(a[pt] < amin) ans[pt] = max(0,min(X,ansmin+z*(t[pt]-r[K])));
		else if(a[pt] > amax) ans[pt] = max(0,min(X,ansmax+z*(t[pt]-r[K])));
		else ans[pt] = max(0,min(X,a[pt]+sum+z*(t[pt]-r[K])));
		++pt;
	}
	repp(i,0,Q){
		printf("%d\n" , ans[i]);
	}
	return 0;
}

解法まとめ

 時系列に沿って処理する。 パーツAが途中で空になった場合、パーツBが途中で空になった場合、いずれでもない場合の3つに$a_i$を分類し、それぞれの場合の答えを保持しながらクエリに答える。(18-44行)

ARC082の他の問題 C D E

自己紹介

プログラミングとか合成音声とか
詳しくはこちら
twitter

プライバシーポリシー

個人情報利用についてはこちら

最終更新日:2023.03.05

お問い合わせ

このページに関するお問い合わせはこちら

競技プログラミングMENU

問題を解くまでの道のり

その他

全体MENU

寄付モドキ

0冂から10000冂まで
詳しくはこちら