kmjp's blog

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

yukicoder : No.1061 素敵な数列

難しい…。
https://yukicoder.me/problems/no/1061

問題

正整数Nが与えられる。
0~(N-1)が3回ずつ現れる数列で、以下を満たすものを1つ構築せよ。

  • 値iが登場するindexがx,y,zの時、||y-x|-|z-y||=i

解法

Editorialそのままで。

ある数の区間を網羅する数列(タイプA)と、1か所だけ穴がある数列(タイプB)を再帰的に構築する。
後者は2個合わせて穴が埋まるようにしよう。

以下n=floor(N/2)とする。

  • タイプAの構築法
    • Nが奇数の時、(N-1,N-1,N-2,N-2,...,n,n,N-1,N-2,...n)を作るとn~(N-1)に関し条件を満たすものが作れるので、これを解の末尾に加える。あとはN=nとして再帰的にタイプAを作る。
    • Nが偶数の時、(n,n+1,...,N-1,N-1,n,n,n+1,n+1,...N-2,N-2,X,N-1)を作るとn~(N-1)に関し、後ろから2つ目が未確定の状態の数列を作れるので、これを解の末尾に加える。あとはN=nとして再帰的にタイプBを作る。
  • タイプBの構築法
    • Nが奇数の時、上記タイプAと同じものを作り、先頭に追加しよう(末尾から2番目の穴をふさがないため)。あとはN=nとして再帰的にタイプBを作る。
    • Nが偶数の時、上記タイプAを反転させたものを作り、穴をふさぐように配置しよう。あとはN=nとして再帰的にタイプAを作る。

注意点として、N=2の時のAとN=1の時のBは解がない。
そこで、Nが小さくなってきたら総当たりで作った解などを埋め込むようにする。

int N;

string A[6]={
	"",
	"000",
	"",
	"000121122",
	"000113122332",
	"000112134423324",
};
string B[6]={
	"",
	"",
	"111000",
	"002011212",
	"001012312233",
	"001011234223344",
};

deque<int> V;
void goB(int N);
void goA(int N){
	if(N<=5) {
		FORR(v,A[N]) V.push_back(v-'0');
		return;
	}
	
	int i;
	if(N%2==1) {
		for(i=N-1;i>=N/2;i--) {
			V.push_back(i);
			V.push_back(i);
		}
		for(i=N-1;i>=N/2;i--) {
			V.push_back(i);
		}
		goA(N/2);
	}
	else {
		for(i=N/2;i<N;i++) V.push_back(i);
		V.push_back(N-1);
		for(i=N/2;i<N;i++) {
			V.push_back(i);
			V.push_back(i);
		}
		goB(N/2);
	}
	
}
void goB(int N){
	if(N<=5) {
		V[V.size()-2]=B[N][0]-'0';
		int i;
		for(i=1;i<B[N].size();i++) V.push_back(B[N][i]-'0');
		return;
	}
	
	int i;
	if(N%2==1) {
		for(i=N/2;i<N;i++) {
			V.push_front(i);
		}
		for(i=N/2;i<N;i++) {
			V.push_front(i);
			V.push_front(i);
		}
		goB(N/2);
	}
	else {
		V[V.size()-2]=N-1;
		for(i=N-2;i>=N/2;i--) {
			V.push_back(i);
			V.push_back(i);
		}
		V.push_back(N-1);
		for(i=N-1;i>=N/2;i--) V.push_back(i);
		goA(N/2);
	}
}

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>N;
	if(N==2) return _P("-1\n");
	goA(N);
	FORR(v,V) cout<<v<<" ";
	cout<<endl;
	
	
	
	
}

まとめ

こういうのすんなり思いつけないんだよな。