题目描述
“War\\texttt{War}War” 是一款经典的棋盘游戏,在巴西相当有名。游戏中,玩家们共同统治整个世界,每位玩家控制一个或多个领地。每位玩家可能有不同的目标,但通常领地越多越好,所有人都为此而战。
每个领地被其拥有者的若干 “军队” 占领。每回合,玩家可以用一个领地上的军队攻击相邻领地并试图征服它。攻击时,进攻方至少要在原领地留下 111 个军队,因此若进攻方有 aaa 个军队,最多只能用 a−1a – 1a−1 个去攻击,且单次战斗中双方最多只能使用 333 个军队。也就是说,无论进攻方领地有多少军队(≥4\\ge 4≥4),每次攻击最多使用 333 个;防守方同样最多使用 333 个。
战斗通过投掷普通 666 面骰子进行。进攻方和防守方各自投掷与所用军队数相等的骰子(最多 333 个)。然后按以下规则决定双方损失的军队数:
玩家可以在同一回合内对一个敌方领地发起连续多次攻击,每次使用剩余的军队并遵守每次最多 333 个的限制。通常玩家会尽量多用军队,但也有一些 “神经质” 的玩家,一旦开始攻击某个敌方领地,就会一直攻击下去,直到征服该领地(防守方军队为 000) 或 无法继续攻击(进攻方只剩 111 个军队) 为止。
Elbdson\\texttt{Elbdson}Elbdson 就是这样的神经质玩家,同时他也是个优秀的程序员。因此,当他邀请朋友玩 War\\texttt{War}War 时,第一件事就是写程序计算自己进攻时的优势。换句话说,Elbdson\\texttt{Elbdson}Elbdson 想知道:为了攻击一个拥有 XXX 个军队的敌方领地,并且有超过 50%50\\%50% 的概率征服它,他至少需要在该领地拥有多少个军队。现在请你完成同样的计算。
输入格式
输入包含多个测试集,每个测试集为一行一个整数 XXX(1≤X≤10001 \\le X \\le 10001≤X≤1000),表示敌方领地的军队数。输入以 X=0X = 0X=0 结束,该行不处理。
输出格式
对于每个测试集,输出一行一个整数,即 Elbdson\\texttt{Elbdson}Elbdson 需要的最少初始军队数,使得征服概率大于 0.50.50.5。
样例
输入
1
2
3
0
输出
3
4
6
题目分析
本题是一个概率计算问题,核心在于模拟随机战斗过程,并求出给定防守方军队数 XXX 时,最小的进攻方初始军队数 NNN,使得从状态 (N,X)(N, X)(N,X) 出发,最终征服(防守方军队降为 000)的概率大于 0.50.50.5。
战斗过程是马尔可夫链,状态由 (a,d)(a, d)(a,d) 表示,其中 aaa 为进攻方军队数,ddd 为防守方军队数,且 d>0d > 0d>0。每轮战斗:
- 进攻方可使用的骰子数 A=min(3,a−1)A = \\min(3, a – 1)A=min(3,a−1)(必须保留 111 个在原领地);
- 防守方可使用的骰子数 D=min(3,d)D = \\min(3, d)D=min(3,d);
- 实际比较次数 m=min(A,D)m = \\min(A, D)m=min(A,D)。
投掷 AAA 个进攻骰子和 DDD 个防守骰子后,根据比较结果,统计进攻方获胜的次数 kkk(0≤k≤m0 \\le k \\le m0≤k≤m)。则:
- 防守方损失 kkk 个军队;
- 进攻方损失 m−km – km−k 个军队(因为每一对比较必有一方损失)。
新的状态为 (a−(m−k), d−k)(a – (m – k),\\; d – k)(a−(m−k),d−k)。若 d−k=0d – k = 0d−k=0,则进攻方获胜;若 a−(m−k)≤1a – (m – k) \\le 1a−(m−k)≤1 且 d−k>0d – k > 0d−k>0,则进攻方失败(无法继续攻击)。
由于骰子结果独立且等概率,我们可以预先计算出对于每对 (A,D)(A, D)(A,D),进攻方获胜次数 kkk 的概率分布 prob[A][D][k]\\textit{prob}[A][D][k]prob[A][D][k]。然后使用动态规划从状态 (a,d)(a, d)(a,d) 转移到更小的状态(因为 aaa 和 ddd 都是非增的,且至少有一个严格减少),最终求出获胜概率。
直接对每个测试用例独立 DP\\texttt{DP}DP,最大 XXX 为 100010001000,而答案 NNN 可能达到数千,重复计算会超时。因此我们采用一次性预处理所有 XXX 的策略,按进攻方军队数 aaa 递增的顺序,滚动数组计算所有 ddd 的获胜概率,并在过程中记录每个 XXX 首次满足概率 >0.5> 0.5>0.5 时的 aaa 值。
解题思路
1. 预计算转移概率
对于所有可能的 A,D∈{1,2,3}A, D \\in \\{1, 2, 3\\}A,D∈{1,2,3},枚举所有骰子结果组合。进攻方有 6A6^A6A 种结果,防守方有 6D6^D6D 种结果,总共最多 63×63=466566^3 \\times 6^3 = 4665663×63=46656 种组合。对每种组合,分别排序后比较前 m=min(A,D)m = \\min(A, D)m=min(A,D) 对,统计进攻方胜利的次数 kkk。最后将频数除以总数,得到概率 prob[A][D][k]\\textit{prob}[A][D][k]prob[A][D][k]。
这一步只需做一次,可以在程序开始时完成。
2. 动态规划状态定义
令 dp[a][d]\\textit{dp}[a][d]dp[a][d] 表示当进攻方有 aaa 个军队、防守方有 ddd 个军队时,进攻方最终征服的概率。边界条件:
- 若 d=0d = 0d=0,则已征服,概率为 111(但我们的状态始终保证 d>0d > 0d>0,转移时处理);
- 若 a≤1a \\le 1a≤1 且 d>0d > 0d>0,则无法攻击,概率为 000。
对于 a≥2,d≥1a \\ge 2, d \\ge 1a≥2,d≥1,转移公式为:
dp[a][d]=∑k=0mprob[A][D][k]×{1,d−k=0,0,a−(m−k)≤1 且 d−k>0,dp[a−(m−k)][d−k],otherwise.
\\textit{dp}[a][d] = \\sum_{k=0}^{m} \\textit{prob}[A][D][k] \\times
\\begin{cases}
1, & d – k = 0,\\\\
0, & a – (m-k) \\le 1 \\text{ 且 } d – k > 0,\\\\
\\textit{dp}[a – (m-k)][d – k], & \\text{otherwise}.
\\end{cases}
dp[a][d]=k=0∑mprob[A][D][k]×⎩⎨⎧1,0,dp[a−(m−k)][d−k],d−k=0,a−(m−k)≤1 且 d−k>0,otherwise.
其中 A=min(3,a−1)A = \\min(3, a-1)A=min(3,a−1),D=min(3,d)D = \\min(3, d)D=min(3,d),m=min(A,D)m = \\min(A, D)m=min(A,D)。
3. 计算顺序与滚动数组
注意到转移后的状态 (a′,d′)(a', d')(a′,d′) 满足 a′≤aa' \\le aa′≤a,d′≤dd' \\le dd′≤d,且至少一个严格减小(因为 kkk 和 m−km-km−k 不能同时为 000)。因此,我们可以按 aaa 从小到大递增,对于每个 aaa,再按 ddd 从小到大递增计算,这样所有依赖的状态 a′<aa' < aa′<a 或 a′=aa' = aa′=a 且 d′<dd' < dd′<d 都已经计算完毕。
因为 aaa 可能达到数千,但转移只依赖最近 444 个 aaa 值(因为 a−(m−k)a – (m-k)a−(m−k) 与 aaa 的差最大为 m≤3m \\le 3m≤3),所以可以用滚动数组 dp[4][d]\\textit{dp}[4][d]dp[4][d],其中第一维为 a mod 4a \\bmod 4amod4。
4. 一次性预处理所有答案
读入所有查询的 XXX,记录最大值 maxX\\textit{maxX}maxX。然后从 a=2a = 2a=2 开始递增,对每个 aaa 计算所有 d∈[1,maxX]d \\in [1, \\textit{maxX}]d∈[1,maxX] 的 dp[a mod 4][d]\\textit{dp}[a \\bmod 4][d]dp[amod4][d]。每计算完一个 aaa,检查所有尚未找到答案的查询 XXX,若 dp[a mod 4][X]>0.5\\textit{dp}[a \\bmod 4][X] > 0.5dp[amod4][X]>0.5,则记录答案 aaa,并标记该查询已解决。当所有查询都找到答案时,停止循环。
5. 正确性说明
- 预计算的概率分布准确反映了单轮战斗的随机性。
- 动态规划转移方程完整描述了所有可能的战斗结果及其后续概率。
- 因为状态空间随着 aaa 和 ddd 单调减小,循环顺序正确,滚动数组保留了必要的历史信息。
- 当 aaa 增大时,获胜概率单调不减(因为更多军队总是更有利),因此第一个满足条件的 aaa 就是最小值。
6. 复杂度分析
- 预计算:枚举 3×33 \\times 33×3 对 (A,D)(A, D)(A,D),每对枚举 6A×6D≤466566^A \\times 6^D \\le 466566A×6D≤46656 种组合,排序和比较开销很小,总时间常数。
- 动态规划:假设最大所需 aaa 为 MMM(实测不超过 100001000010000),则状态数为 M×maxXM \\times \\textit{maxX}M×maxX,每个状态转移最多 444 种 kkk(因为 m≤3m \\le 3m≤3),总时间复杂度 O(M⋅maxX⋅4)O(M \\cdot \\textit{maxX} \\cdot 4)O(M⋅maxX⋅4),其中 MMM 约为 200002000020000 以内,maxX≤1000\\textit{maxX} \\le 1000maxX≤1000,完全可行。
- 空间复杂度:滚动数组 O(4⋅maxX)O(4 \\cdot \\textit{maxX})O(4⋅maxX)。
代码实现
// Playing War
// UVa ID: 11061
// Verdict: Accepted
// Submission Date: 2026-06-21
// UVa Run Time: 0.640s
//
// 版权所有(C)2026,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
double prob[4][4][4]; // prob[A][D][k]:A个攻击骰子、D个防御骰子时,攻击方胜k次的概率
// 生成所有长度为num的骰子点数(1~6)组合
void genRolls(int num, vector<int>& cur, vector<vector<int>>& all) {
if ((int)cur.size() == num) {
all.push_back(cur);
return;
}
for (int i = 1; i <= 6; ++i) {
cur.push_back(i);
genRolls(num, cur, all);
cur.pop_back();
}
}
// 预计算所有(A,D)组合下的概率分布
void precompute() {
for (int A = 1; A <= 3; ++A) {
for (int D = 1; D <= 3; ++D) {
vector<vector<int>> att, def;
vector<int> cur;
genRolls(A, cur, att);
genRolls(D, cur, def);
int total = (int)att.size() * (int)def.size();
int m = min(A, D);
vector<int> cnt(m + 1, 0);
for (auto& a : att) {
sort(a.begin(), a.end(), greater<int>());
for (auto& d : def) {
sort(d.begin(), d.end(), greater<int>());
int win = 0;
for (int i = 0; i < m; ++i)
if (a[i] > d[i]) ++win;
++cnt[win];
}
}
for (int k = 0; k <= m; ++k)
prob[A][D][k] = (double)cnt[k] / total;
}
}
}
int main() {
precompute();
vector<int> queries;
int x;
while (cin >> x && x != 0)
queries.push_back(x);
if (queries.empty()) return 0;
int maxX = *max_element(queries.begin(), queries.end());
vector<int> ans(maxX + 1, 0);
vector<bool> found(maxX + 1, false);
// dp[4][maxX+1] 滚动数组,dp[i][d] 表示进攻方军队数为 i(取模4)时的概率
vector<vector<double>> dp(4, vector<double>(maxX + 1, 0.0));
int foundCount = 0;
for (int a = 2; ; ++a) {
int A = min(3, a – 1);
int curIdx = a % 4;
for (int d = 1; d <= maxX; ++d) {
int D = min(3, d);
int m = min(A, D);
double sum = 0.0;
for (int k = 0; k <= m; ++k) {
int la = m – k; // 进攻方损失
int ld = k; // 防守方损失
double p = prob[A][D][k];
if (d – ld == 0) sum += p;
else if (a – la <= 1) sum += 0.0;
else sum += p * dp[(a – la) % 4][d – ld];
}
dp[curIdx][d] = sum;
}
// 检查是否有查询首次满足条件
for (int X : queries) {
if (!found[X] && dp[curIdx][X] > 0.5 + 1e-9) {
found[X] = true;
ans[X] = a;
++foundCount;
}
}
if (foundCount == (int)queries.size()) break;
// 安全上限(理论上足够,防止意外)
if (a > 100000) break;
}
for (int X : queries)
cout << ans[X] << '\\n';
return 0;
}
总结
本题的核心是概率动态规划,结合了预计算转移概率和滚动数组优化。关键点包括:
- 准确建模战斗规则,注意单次战斗最多使用 333 个骰子的限制。
- 利用马尔可夫链的无后效性,按状态大小递增顺序递推。
- 一次性预处理所有查询,避免重复计算,是应对多组数据的高效策略。
- 浮点数比较时需设置微小精度误差容忍,避免边界判断失误。
此类问题常见于模拟类概率计算,掌握状态转移的构建和计算顺序优化是解题的关键。
网硕互联帮助中心![C++入门篇(十):string(上)——认识string:构造与三大遍历(一条龙讲透operator[]、迭代器、auto、范围for)-网硕互联帮助中心](https://www.wsisp.com/helps/wp-content/uploads/2026/10/20261001032318-6abdd226d398f.png)




评论前必须登录!
注册