CF717D 博弈
1 博弈
2 题目描述
时间限制 \(1s\) | 空间限制 \(256M\)
给出 \(n\) 堆物品,每个选手每次可以从一堆物品中拿走至少一个,最多所有的物品,如果一个人没有东西可以取了,那么他就胜利了。一堆物品中最多有\(x\)个物品。我们并不知道每一堆中物品的具体数量,我们只知道对于每一堆物品来说,其中有 \(i\) 件物品的可能性为 \(P(i)\)。现给出 \(0\) 到 \(x\) 的数量出现的可能性,求出先手获胜的概率。
3 输入格式
第一行包括两个整数 \(n\) 和 \(x\)(\(1 \le n \le 10^9, 1 \le x \le 100\))。
第二行包括 \(x + 1\) 个保留 \(6\) 位的小数 \(P(i)\)。保证 \(\sum_{i = 1} ^ {x+1} P(i) = 1\)。
4 输出格式
一行一个小数,表示先手获胜的概率。如果输出结果与正确结果的差的绝对值小于等于 \(10^{-6}\),判断答案正确。
5 样例输入输出
样例1输入 | 样例1输出 |
---|---|
2 2 0.500000 0.250000 0.250000 |
0.62500000 |
6 题解
前置知识:当先手必胜时,所有的堆中的物品个数必定满足 \(\bigoplus_{i = 1}^{n}\limits a_i \ne 0\)。
根据必胜时的堆数分布,可以列出以下状态:
设 \(dp_{i,j}\) 表示前 \(i\) 堆物品个数异或和为 \(j\) 的概率。
此时的转移方程为:
\(dp_{i,j} = \sum_{k=0}^{127}\limits (dp_{i-1,k} * P_{j \oplus k})\)
我们发现,直接计算上述式子的时间复杂度为 \(O(128^2 * n)\),而由于此题中 \(1 \le n \le 10^9\),时间复杂度明显过高。我们发现以上式子与矩阵乘法中的 \(c_{i,j} = \sum_{k=1}^{s}\limits (a_{i,k} * b_{k, j})\) 十分相似:我们只需要将 \(P_{j \oplus k}\) 看作是 \(b_{k,j}\) 即可,得出的矩阵 \(A\) 满足 \(A_{i, j} = P_{i\oplus j}\)(异或满足交换律)。这时我们将每一个 \(i\) 看作是一个新的矩阵进行计算,得到的答案矩阵就是单位矩阵(\(dp\)的初值)乘上 \(A^{n}\)。\(A^n\) 可以在 \(O(n*log_2n)\) 时间复杂度内使用矩阵快速幂求出。我们总共的时间复杂度也就是 \(O(n * log_2n)\)。
由于我们对于所有的 \(i\) 都计算出了一个新的矩阵,我们最终答案矩阵的 \(ans_{0,0}\) 这一个值代表的就是前 \(n\) 堆物品个数异或和为 \(0\) 的概率。然后因为必胜的前提是异或和必须不为 \(0\),最终输出的答案就是 \(1 - ans_{0, 0}\)。
7 代码(空格警告):
#include <iostream>
#include <cstring>
using namespace std;
const int N = 105;
const int M = 140;
int n, x;
double p[M];
double A[M][M], b[M][M];
double ans[M][M];
void times1()
{
memset(b, 0, sizeof(b));
for (int i = 0; i <= 127; i++)
{
for (int j = 0; j <= 127; j++)
{
for (int k = 0; k <= 127; k++)
{
b[i][j] += double(A[i][k] * A[k][j]);
}
}
}
for (int i = 0; i <= 127; i++)
{
for (int j = 0; j <= 127; j++)
{
A[i][j] = b[i][j];
}
}
}
void times2()
{
memset(b, 0, sizeof(b));
for (int i = 0; i <= 127; i++)
{
for (int j = 0; j <= 127; j++)
{
for (int k = 0; k <= 127; k++) b[i][j] += (ans[i][k] * A[k][j]);
}
}
for (int i = 0; i <= 127; i++) for (int j = 0; j <= 127; j++) ans[i][j] = b[i][j];
}
int main()
{
memset(A, 0, sizeof(A));
memset(ans, 0, sizeof(ans));
cin >> n >> x;
for (int i = 0; i <= x; i++) cin >> p[i];
for (int i = 0; i <= 127; i++)
{
for (int j = 0; j <= 127; j++)
{
A[i][j] = p[i ^ j];
}
}
for (int i = 0; i <= 127; i++) ans[i][i] = 1;
while (n)
{
if (n & 1) times2();
times1();
n >>= 1;
}
cout << 1 - ans[0][0];
return 0;
}