Official

O - 色分けトーナメント/Red Blue Tournament Editorial by mechanicalpenciI


まず、第 \(1\) ラウンドの第 \(i\) 試合で選手 \(P_{2i-1}\) が勝つ、または 第 \(2\) ラウンド以降の第 \(i\) 試合で その直前のラウンドの第 \(2i-1\) 試合の勝者が勝つことを「左が勝つ」, そちらではない選手 が勝つことを「右が勝つ」と表現することにします。

全体で行われる \(2^N-1\) 試合の中で左右どちらが勝つか決まっているとき、第 \(1\) , \(2\) の条件をみたす色の割り当て方は \(2\) 通り、特に第 \(N\) ラウンドの勝者の色が赤か青かについてちょうど \(1\) 通りずつあります。これは \(N\) についての帰納法でしめすことができます。 \(N=1\) のときについては元々 \(4\) 通りしかありませんから、少し試せば明らかです。そうでないとき、 \(N-1\) で成り立つという仮定から、第 \(N\) ラウンドを除いた条件をみたす割り当て方は \(4\) 通りであり、第 \(N\) ラウンドで戦う選手の色の組み合わせ \(4\) 種類についてちょうど \(1\) 通りずつ存在します。これは \(N=1\) のときと同じ状況であり、第 \(N\) ラウンドの条件もみたす割り当て方は第 \(N\) ラウンドの勝者の色について \(1\) 通りずつ存在します。

次に、 \(i+2\) \((1\leq i\leq M)\)個目の条件について考えます。
選手 \(W_i\)\(L_i\) が戦うためには選手 \(W_i\) , \(L_i\) は互いと当たるまで勝ち続けなければなりません。逆にそうしたとき、この \(2\) 人の選手の試合はどこかで必ず行われます。そうでないと、すべてのラウンドを通じて \(1\) 度も負けない選手が \(2\) 人以上出てしまうからです。この \(2\) 人の試合が第 \(R_i\) ラウンドで行われるとしたとき、その \(2\) 人のそれまでの試合も合わせた \(2R_i-1\) 試合について左右どちらの選手が勝ったかが一意に定まります。逆にそれがみたされているとき、他の試合について左右どちらの選手が勝ったかに関わらずこの条件はみたされます。

以上から、\(i+2\) \((1\leq i\leq M)\)個目の条件によって定まった、各試合において左右どちらの選手が勝ったかの情報が、異なる \(i\) の間で矛盾を起こした時、そのような勝ち上がり方は存在せず、条件をみたす色の割り当て方は \(0\) 通りとなります。そうでない時、これらの情報の和集合で左右どちらが勝つか定まった試合の数を \(D\) とすると、それ以外の試合はどちらが勝ったことにしても良く、それぞれの決め方について割り当て方が \(2\) 通りずつ存在するため、条件をみたす割り当て方は、\(2\times 2^{(2^N-1)-D}=2^{(2^N-D)}\) 通りあることが分かります。

よって、\(i+2\) \((1\leq i\leq M)\)個目の条件によってどの試合においてどちらが勝たなくてはならないという情報が得られたのか調べればよいですが、これは各選手が第 \(1\) ラウンドの第何試合に登場したか調べ ( これは \(Q_{P_i} =i\) となる \((Q_i)\) を用意しておけばよいです ) 、ラウンド順に第何試合に登場したか見ていく事で \(O(N)\) で調べられます。 よって、\(O(NM)\) でこの問題を解くことができました。 これは十分高速です。

C++による実装例:

#include <bits/stdc++.h>
using namespace std;

#define N (1<<17)
#define MOD 998244353
#define rep(i, n) for(int i = 0; i < n; ++i)
#define rep2(i, a, b) for(int i = a; i <= b; ++i)

int a[N];
int p[N];
bool conf;

void check(int k, int x) {
	if (a[k] == -1)	a[k] = x;
	else if (a[k] != x)conf = true;
	return;
}

int main(void) {
	int n, m;
	int x, y;
	long long ans = 2;

	cin >> n >> m;
	n = (1 << n);
	rep(i, n) {
		cin >> x;
		p[x - 1] = n + i;
	}

	rep(i, n)a[i] = -1;
	conf = false;

	rep(i, m) {
		cin >> x >> y;
		x = p[x - 1];
		y = p[y - 1];
		while ((x / 2) != (y / 2)) {
			check(x / 2, x % 2);
			check(y / 2, y % 2);
			x /= 2;
			y /= 2;
		}
		check(x / 2, x % 2);
	}

	rep2(i, 1, n - 1) {
		if (a[i] == -1)ans = (ans * 2) % MOD;
	}
	if (conf)cout << 0 << endl;
	else cout << ans << endl;
	return 0;
}

posted:
last update: