kmjp's blog

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

yukicoder : No.1031 いたずら好きなお姉ちゃん

想定が平方分割なのは想定外だった…。
https://yukicoder.me/problems/no/1031

問題

1~NのPermutationである数列が与えられる。
このうち長さ2以上の区間を選び、その中の最大値と最小値を入れ替えるという動作を行う。
得られる数列は何通りか。

解法

入れ替え云々は置いておいて、区間を選んだ時の最大値と最小値の組み合わせを求めればよい。
最大値の大きな順に処理していくことを考える。
今見ている(最大値とする)要素を右端として、左端を左に伸ばしたとき、最小値が何回更新されるかというのと(ただし途中で自身より大きな値より左に伸ばすことができない)、同様に今見ている要素を左端として、右端を右に伸ばしたとき、最小値が何回更新されるかというのを求めればよい。
そのためには、自身に最寄りの自身より小さい値の位置を、左右それぞれ求めておこう。
さらにそれらをダブリングしておくと、二分探索の要領で左右それぞれ最小値が何回更新されるかO(logN)で求められるようになる。

int N;
int P[101010];
int L[101010][21],R[101010][21];

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>N;
	vector<pair<int,int>> cand;
	FOR(i,N) {
		cin>>P[i+1];
		cand.push_back({P[i+1],i+1});
	}
	FOR(j,20) R[N+1][j]=N+1;
	vector<int> V;
	FOR(i,N+1) {
		while(V.size()&&P[V.back()]>P[i]) V.pop_back();
		if(V.size()) L[i][0]=V.back();
		V.push_back(i);
	}
	V.clear();
	V.push_back(N+1);
	for(i=N;i>=1;i--) {
		while(V.size()&&P[V.back()]>P[i]) V.pop_back();
		if(V.size()) R[i][0]=V.back();
		V.push_back(i);
	}
	FOR(i,20) FOR(j,N) L[j+1][i+1]=L[L[j+1][i]][i],R[j+1][i+1]=R[R[j+1][i]][i];
	
	set<int> D;
	D.insert(0);
	D.insert(N+1);
	sort(ALL(cand));
	reverse(ALL(cand));
	ll ret=0;
	FORR(c,cand) {
		x=c.second;
		auto it=D.insert(x).first;
		i=*prev(it);
		j=*next(it);
		int cur=x;
		for(y=19;y>=0;y--) if(R[cur][y]<j) cur=R[cur][y],ret+=1<<y;
		cur=x;
		for(y=19;y>=0;y--) if(L[cur][y]>i) cur=L[cur][y],ret+=1<<y;
	}
	cout<<ret<<endl;
	
}

まとめ

こういうのCSAやCodeforcesで多いイメージ。