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;
}

欢迎关注我的公众号:智子笔记

posted @ 2021-02-05 16:32  David24  阅读(67)  评论(0编辑  收藏  举报