kmjp's blog

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

yukicoder : No.738 平らな農地

平衡二分探索木は思いつきもしなかったです…。
https://yukicoder.me/problems/no/738

問題

整数列Aが与えられる。
ある要素の値aをbに変化させるコストは|b-a|とする。

A中である連続したK要素の部分列が同一の値を持つようにするための最小コストはいくつか。

解法

部分列をスライドさせながら総当たりし、それぞれのコストを求める。
このようなコストを持つ問題においては、K要素をそろえるコストはK要素の中央値に合わせるのがベストである。
そこで、連続した部分列の中央値を求める方法と、中央値を求めたときにそこにそろえるコストの総和を高速に求められれば良い。

自分はいずれもBITを用いた。
1つのBITは、N要素中今見ているK個の要素を管理するためのBITで、0/1の2値を取る。
もう一つは、そのK要素におけるA[i]の総和を取る。

まず、数列をA[i]の要素順にソートし、各要素をBIT中のインデックスを対応させる。

この状態で前者をBITを値が(K/2)となるよう二分探索することで中央値となる要素A[x]を特定できる。
あとは、中央値を超えるの要素について、後者のBITで総和を取りA[x]を要素数分引いたものと、中央値を未満の要素について、後者のBITで総和を取りA[x]から要素数分引いたものの和がコストとなる。
以下のhoge関数がこの処理に対応する。

template<class V, int ME> class BIT {
public:
	V bit[1<<ME],val[1<<ME];
	V operator()(int e) {if(e<0) return 0;V s=0;e++;while(e) s+=bit[e-1],e-=e&-e; return s;}
	void add(int e,V v) { val[e++]+=v; while(e<=1<<ME) bit[e-1]+=v,e+=e&-e;}
	int lower_bound(V val) {
		V tv=0; int i,ent=0;
		for(i=ME-1;i>=0;i--) if(tv+bit[ent+(1<<i)-1]<val) tv+=bit[ent+(1<<i)-1],ent+=(1<<i);
		return ent;
	}
};
BIT<ll,20> num,tot;

int N,K;
int A[101010];
int R[101010];
pair<ll,int> P[101010];

ll hoge() {
	int p=(K+1)/2;
	int x=num.lower_bound(p);
	
	ll ret=0;
	ret+=(p-1)*P[x].first-tot(x-1);
	ret+=(tot(N)-tot(x))-(K-p)*P[x].first;
	return ret;
	
}

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>N>>K;
	FOR(i,N) {
		cin>>A[i];
		P[i]={A[i],i};
	}
	sort(P,P+N);
	FOR(i,N) R[P[i].second]=i;
	
	ll mi=1LL<<60;
	FOR(i,K-1) {
		num.add(R[i],1);
		tot.add(R[i],A[i]);
	}
	for(i=0;i+K<=N;i++) {
		num.add(R[i+K-1],1);
		tot.add(R[i+K-1],A[i+K-1]);
		mi=min(mi,hoge());
		num.add(R[i],-1);
		tot.add(R[i],-A[i]);
	}
	cout<<mi<<endl;
}

まとめ

Treapとかあんまり使ったことないので、どこで生きるかよく知らないんだよね。