以弱胜强的秘密
京东技术岗 8月8号笔试 第二题
题目内容
在一场单败淘汰赛中,共有 N=2mN = 2^mN=2m 名选手参加。他们的能力值恰好是 111 到 NNN 的每一个整数,没有重复。比赛采用固定的淘汰规则:
- 首先,将所有选手按照某种初始顺序排成一行。
- 每一轮,将当前序列中相邻的两人配对对战:第 111 名对第 222 名,第 333 名对第 444 名,以此类推。
- 若能力值为 xxx 的选手与能力值为 yyy 的选手对战,则 xxx 获胜的概率为 xx+y\frac{x}{x+y}x+yx。
- 每轮结束后,胜者保持原有相对顺序,形成新的序列,继续下一轮,直至产生唯一的冠军。
现在,你希望能力值为 111 的选手获得最终冠军的概率尽可能大。请问:在所有可能的初始排列中,有多少种排列方式可以使得能力值为 111 的选手的夺冠概率达到最大值?答案请对 998244353998244353998244353 取模。
数据范围:mmm 满足 1≤m≤121 \le m \le 121≤m≤12。
输入描述
输入包含一行一个整数 mmm(1≤m≤121 \le m \le 121≤m≤12),表示选手总数为 2m2^m2m。
输出描述
输出一行一个整数,表示满足条件的初始排列数量对 998244353998244353998244353 取模后的结果。
样例1
输入
1
输出
2
说明
当 m=1m=1m=1 时,总选手数 N=21=2N=2^1=2N=21=2,能力值为 1 和 2。无论怎样排列,能力值为 1 的选手夺冠概率均为 11+2=13\frac{1}{1+2}=\frac{1}{3}1+21=31,即所有 2!=22! = 22!=2 种排列都能使夺冠概率达到最大值。因此答案为 2。
样例2
输入
3
输出
128
说明
总选手数 N=23=8N=2^3=8N=23=8。要使能力值为 1 的选手夺冠概率最大,必须使其在每一轮中对阵的对手尽可能弱。可以证明,满足条件的初始排列总数为 2N−1=27=1282^{N-1}=2^7=1282N−1=27=128。对 998244353998244353998244353 取模后仍为 128。
样例3
输入
4
输出
32768
说明
总选手数 N=24=16N=2^4=16N=24=16。同理,最优排列数量为 2N−1=215=327682^{N-1}=2^{15}=327682N−1=215=32768,取模后结果为 32768。
思路
解题思路: 数学原理 + 逻辑分析
-
最佳答案就为
2^(N - 1), 其中N = 2^m,然后对 998244353取模即可。-
为了让1最终获胜概率最大,较强的选手尽量在1到达它们之前相互比赛。以m=3为例子,期待的比较方式为
冠军 ● / \ ● ● / \ / \ ● ● ● ● / \ / \/ \ / \ 1 2 3 4 5 6 7 8
-
-
可以很容易看出比较过程为完全二叉树,并且二叉树的叶子节点数量为
N, 对于完全二叉树,内部节点数量为N-1, 并且每个内部节点左节点和右节点可以交换顺序,不影响最终结果,那么方案数就为2^(N-1) -
求
2^(N-1)可使用快速幂算法。
C++
cpp
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 998244353;
ll qpow(ll a, ll b) {
ll res = 1;
while (b) {
if (b & 1) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int m;
cin >> m;
int N = 1 << m;
// 最优排列对应一棵完全二叉树
// 每个内部节点的左右子树可以交换
// N 个叶子共有 N-1 个内部节点
// 因此答案为 2^(N-1)
cout << qpow(2, N - 1) << '\n';
return 0;
}
java
java
import java.io.*;
import java.util.*;
public class Main {
static final long MOD = 998244353L;
static long qpow(long a, long b) {
long res = 1;
while (b > 0) {
if ((b & 1) != 0) {
res = res * a % MOD;
}
a = a * a % MOD;
b >>= 1;
}
return res;
}
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int m = Integer.parseInt(br.readLine().trim());
int N = 1 << m;
// 最优排列对应一棵完全二叉树
// 每个内部节点的左右子树可以交换
// N 个叶子共有 N-1 个内部节点
// 因此答案为 2^(N-1)
System.out.println(qpow(2, N - 1));
}
}
python
python
import sys
MOD = 998244353
def qpow(a, b):
res = 1
while b:
if b & 1:
res = res * a % MOD
a = a * a % MOD
b >>= 1
return res
m = int(input())
N = 1 << m
# 最优排列对应一棵完全二叉树
# 每个内部节点的左右子树可以交换
# N 个叶子共有 N-1 个内部节点
# 因此答案为 2^(N-1)
print(qpow(2, N - 1))
javascript
js
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const MOD = 998244353n;
function qpow(a, b) {
let res = 1n;
while (b > 0) {
if (b & 1) {
res = res * a % MOD;
}
a = a * a % MOD;
b = Math.floor(b / 2);
}
return res;
}
rl.on("line", (line) => {
const m = Number(line.trim());
const N = 1 << m;
// 最优排列对应一棵完全二叉树
// 每个内部节点的左右子树可以交换
// N 个叶子共有 N-1 个内部节点
// 因此答案为 2^(N-1)
console.log(qpow(2n, N - 1).toString());
rl.close();
});
Go
go
package main
import (
"bufio"
"fmt"
"os"
)
const MOD int64 = 998244353
func qpow(a, b int64) int64 {
var res int64 = 1
for b > 0 {
if b&1 != 0 {
res = res * a % MOD
}
a = a * a % MOD
b >>= 1
}
return res
}
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var m int
fmt.Fscan(in, &m)
N := 1 << m
// 最优排列对应一棵完全二叉树
// 每个内部节点的左右子树可以交换
// N 个叶子共有 N-1 个内部节点
// 因此答案为 2^(N-1)
fmt.Fprintln(out, qpow(2, int64(N-1)))
}