kmjp's blog

競技プログラミング参加記です

みんなのプロコン予選 : D - 工場

うーん、最近AtCoder系での成績がひどいぞ…。
http://yahoo-procon2017-qual.contest.atcoder.jp/tasks/yahoo_procon2017_qual_d

問題

工場は1日にK個の商品を作るとする。
以下のクエリiに順次答えよ。

  • D[i]日目にA[i]個の注文が入った。工場にある商品のうち、(在庫があれば)最大A[i]個まで販売可能である。
  • ここまでのクエリで入った注文に対し、D[i]目までの注文に答えて最適に商品を販売する場合、最大何個販売できるか答えよ。

解法

D[i]目までの生産量はD[i]*Kで容易にわかる。
D[i]日目までの注文に際し、在庫がある限りできるだけ多く売るのが最適なのは容易にわかる。
あとはその際D[i]に何個在庫があるかを求められれば、差を取って販売数がわかる。

ここで問題は、在庫がない限り売れないことである。
在庫がマイナスの値を取ることが可能ならば、各日の(生産量-注文数)の総和を取っていけばよい。
ただ、実際は途中在庫がマイナスになることは許されないので、(生産量-注文数)はそれよりも多くなることが考えられる。

d日目の(生産量-注文数)をB[d]とする。
在庫マイナスを無視すれば、日付半開区間[L,R)に突入する直前(L-1)日目終了時の在庫がZ個の場合、R日目開始前の在庫はZ+sum(B[L...R))となる。
しかし途中でマイナスになることを避けるならば、一時的に販売量が落ちるので在庫はZ+sum(B[L...R))より大きくなる。
途中でマイナスになりかねないタイミングが複数あった場合、1回でもマイナスを回避するために販売数を絞ると、あとの商品の増減は固定される。
よってマイナスになるタイミングは区間[L,R)で在庫数が最低になるタイミングの1回だけを考えればよく、その場合R日目開始前の在庫も1通りしかない。
そこで区間C=[L,R)に対しパラメータX[C]、Y[C]を以下のように定める。

  • 途中でマイナスを回避するタイミングがある場合、R日目終了後の在庫はY[C]となる。
  • そうでない場合、区間開始前から区間終了後まで在庫はX[C]増加する。

上記の値は区間開始時の在庫の量で決まるので、結局区間終了時の在庫数はmax(Y[C],Z+X[C])であらわせる。

あとはこのX[C]、Y[C]をSegTreeの要領で更新・連結していけば、2種類目のクエリに対しY[[0,D[i]+1)]がD[i]日目終了時の在庫数となる。
連続する2つの区間C=[P,Q)とC'=[Q,R)を連結した区間C''=[P,R)を考えるとき、X,Yの値は以下のようになる。
区間C終了時の在庫はmax(Y[C],Z+X[C])なので、区間C'終了時の在庫を考えるとmax(Y[C'],max(Y[C],Z+X[C])+X[C'])=max(max(Y[C'],Y[C]+X[C']),Z+X[C]+X[C'])。
よってX[C'']=X[C]+X[C']、Y[C'']=max(Y[C'],Y[C]+X[C'])となる。

int Q;
ll K;
int T[101010],D[101010],A[101010];
vector<ll> V;

template<class V,int NV> class SegTree_1 {
public:
	vector<V> X,Y;
	
	SegTree_1(){
		X=vector<V>(NV*2);
		Y=vector<V>(NV*2);
	}
	pair<ll,ll> getval(int x,int y,int l=0,int r=NV,int k=1) { // x<=i<y
		if(r<=x || y<=l) return {0,0};
		if(x<=l && r<=y) return {X[k],Y[k]};
		auto A=getval(x,y,l,(l+r)/2,k*2);
		auto B=getval(x,y,(l+r)/2,r,k*2+1);
		return {A.first+B.first,max(B.first+A.second,B.second)};
	}
	void diff(int entry, V v) {
		entry += NV;
		X[entry]+=v;
		while(entry>1) {
			entry>>=1;
			X[entry]=X[entry*2]+X[entry*2+1];
			Y[entry]=max(X[entry*2+1]+Y[entry*2],Y[entry*2+1]);
		}
	}
};

SegTree_1<ll,1<<20> st;

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>Q>>K;
	V.push_back(0);
	FOR(i,Q) {
		cin>>T[i];
		if(T[i]==1) {
			cin>>D[i]>>A[i];
		}
		else {
			cin>>D[i];
		}
		V.push_back(D[i]);
	}
	
	sort(ALL(V));
	V.erase(unique(ALL(V)),V.end());
	for(i=1;i<V.size();i++) {
		st.X[i+(1<<20)] += 1LL*K*(V[i]-V[i-1]);
	}
	for(i=(1<<20)-1;i>=1;i--) {
		st.X[i] = st.X[2*i]+st.X[2*i+1];
		st.Y[i] = max(st.X[2*i+1]+st.Y[2*i],st.Y[2*i+1]);
	}
	
	FOR(i,Q) {
		x = lower_bound(ALL(V),D[i])-V.begin();
		if(T[i]==1) {
			st.diff(x,-A[i]);
		}
		else {
			ll tot=1LL*K*D[i];
			auto ret=st.getval(0,x+1);
			cout<<tot-ret.second<<endl;
		}
	}
	
	
}

まとめ

セグメントを連結していくところまでは思いつけたのに、連結する際に取った状態変数がいまいちで、連結処理がうまく書けなかった。
せっかく似たテクをCodeforcesで覚えたのになぁ…。