kmjp's blog

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

キーエンス プログラミング コンテスト 2021 : D - Choosing Up Sides

なんとか解けてよかった。
https://atcoder.jp/contests/keyence2021/tasks/keyence2021_d

問題

正整数Nが与えられる。
2^N人の人がいるので、2^(N-1)人のチーム2つに分かれて、試合をするという作業を繰り返す。
何回か試合を行ったのち、2人の対で同じチームになった回数nと異なるチームになったmがどの対でも一致するようにしたい。
条件を満たす最小試合回数のチーム編成例を示せ。

解法

1試合で同じチームになれる人は2^(N-1)-1人、別のチームになれる人は2^(N-1)人である。
よって、n=2^(N-1)-1、m=2^(N-1)を達成できれば、試合回数が最小になる。
あとはその編成例を考えよう。

今2^k人に対し(2^k)-1試合で条件を満たす方法がわかったとする。
そこから、2^(k+1)人に対し(2^(k+1))-1試合で条件を満たす方法を構築しよう。

これは以下のようにすればよい。
k=3のケースを考える。
8人の組み合わせでは7試合が行われる。これの組み合わせをAとする。
まず、16人を前後半半々に分けたチームを作る(下記xとyに相当)
次に、Aを縦に二重化したものをさらに横に二重化し、14試合分の組み合わせを作る。
この時、二重化したうちの片方はチームを反転させる。(下記図で、A1は元のAの1試合目の組み合わせを示す)

こうすると、前後半に分けたチームは、互いに7回チームが一致し、8回チームが異なる状況を作り出せる。

xxxxxxxxyyyyyyyy
---A1------A1---
---A1------A1'--
---A2------A2---
---A2------A2'--
...
---A7------A7---
---A7------A7'--
int N;
int C[9][512][512];
int D[256][256];

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>N;
	
	C[1][0][0]=0;
	C[1][0][1]=1;
	for(i=2;i<=N;i++) {
		FOR(x,1<<i) C[i][0][x]=x;
		int s=1<<(i-2);
		FOR(x,((1<<i)-2)) {
			FOR(y,s) {
				C[i][x*2+1][y]=C[i][x*2+2][y]=C[i-1][x][y];
				C[i][x*2+1][y+2*s]=C[i][x*2+2][y+2*s]=C[i-1][x][y+s];
				C[i][x*2+1][y+s]=C[i][x*2+2][y+3*s]=C[i-1][x][y]+2*s;
				C[i][x*2+1][y+3*s]=C[i][x*2+2][y+s]=C[i-1][x][y+s]+2*s;
			}
		}
	}
	cout<<((1<<N)-1)<<endl;
	FOR(i,((1<<N)-1)) {
		string S;
		S.resize(1<<N);
		int t=(1<<N)/2;
		FOR(x,t) S[C[N][i][x]]='A';
		FOR(x,t) S[C[N][i][x+t]]='B';
		cout<<S<<endl;
		
	}
}

まとめ

倍々ゲームで対応できるのはすぐ予想がついたが、詰め切るのに手間取った。