2024牛客五一集训派对day1 A. Blackjack【概率DP、回滚背包】

A. Blackjack

题目地址

题意

n n n 张牌,每张牌有一个分数 x i x_i xi,同时给定两个正整数 a , b a,b a,b
现在将所有牌随机均匀地打乱,然后从中顺序地抽牌,抽到的牌的分数将累加进得分 s u m sum sum 中,当得分位于: [ a + 1 , b ] [a + 1, b] [a+1,b] 时赢得比赛,若得分 s u m sum sum 超过 b b b,则输掉比赛。
可以在抽出某张牌后立即停止比赛,当前得分即为最终得分 s u m sum sum
问:如果以最优策略退出,赢得比赛的概率是多少?

数据约定

1 ≤ n ≤ 500 , 1 ≤ a < b ≤ 500 , 1 ≤ x i ≤ 500 1 \leq n \leq 500, 1 \leq a < b \leq 500,1 \leq x_i \leq 500 1n500,1a<b5001xi500

思路

首先我们需要理解最优策略,也就是当得分 s u m sum sum 超过 a a a 时,立即结束游戏,如果此时 s u m ≤ b sum \leq b sumb,则获胜,如果不在此时退出,再选额外的牌可能 s u m sum sum 会超过 b b b;如果此时 s u m > b sum > b sum>b,后面也无法力挽狂澜了。

知道这个最优策略后,我们不难想到用背包来统计一些信息:
d p [ i ] [ j ] [ s u m ] dp[i][j][sum] dp[i][j][sum] 表示前 i i i 个物品,选择了 j j j 个物品,且分数恰好为 s u m sum sum 的概率

那么转移为:
d p [ i ] [ j ] [ s u m ] = d p [ i − 1 ] [ j ] [ s u m ] + d p [ i − 1 ] [ j − 1 ] [ s u m − w [ i ] ] × j n − ( j − 1 ) dp[i][j][sum] = dp[i - 1][j][sum] + dp[i-1][j - 1][sum - w[i]] \times \dfrac{j}{n - (j - 1)} dp[i][j][sum]=dp[i1][j][sum]+dp[i1][j1][sumw[i]]×n(j1)j

第一部分就是直接从前 i − 1 i - 1 i1 个物品转移过来(不取当前 i i i);第二部分则是先在前 i − 1 i - 1 i1 个物品中取 j − 1 j - 1 j1 个物品,加上当前第 i i i 个物品的概率,由于已经取走了 j − 1 j - 1 j1 个物品,还剩下 n − ( j − 1 ) n - (j - 1) n(j1) 个物品,从中取出第 i i i 个物品概率为: 1 n − ( j − 1 ) \frac{1}{n - (j - 1)} n(j1)1,取出第 i i i 个物品后,它有 j j j 个位置可以放(已经取出的 j − 1 j - 1 j1 个物品形成了 j j j空位)。

显然这样子的 d p dp dp 数组内存占用过大,我们可以采用滚动复用的策略将数组压维到: d p [ j ] [ s u m ] dp[j][sum] dp[j][sum],只保留后两维度

然而,此时还不能直接利用 ∀ s u m ∈ [ a + 1 , b ] \forall sum \in [a + 1,b] sum[a+1,b] 来算 a n s ans ans,因为这样子会算重
考虑 a = 3 , b = 100 a = 3, b = 100 a=3,b=100,而 x 1 = 2 , x 2 = 3 , x 3 = 50 x_1 = 2, x_2 = 3, x_3 = 50 x1=2,x2=3,x3=50,显然如果先选了 x 1 x_1 x1 x 2 x_2 x2 后会立即退出游戏,但是如果我们统计 s u m = 2 + 3 + 50 sum = 2 + 3 + 50 sum=2+3+50 d p [ 3 ] [ s u m ] dp[3][sum] dp[3][sum] 的话,我们会在选了 x 1 , x 2 x_1,x_2 x1,x2 的基础上再选上 x 3 x_3 x3,不符合最优的策略(虽然不会超出 b b b

那么我们就要考虑枚举最后一个选择的物品 i i i,使得 s u m sum sum 恰好落在 [ a + 1 , b ] [a + 1, b] [a+1,b]
如何消除?考虑 回滚背包

我们逆着消除当前物品 i i i 的贡献,那么此时 d p dp dp 数组就只有除了 i i i 以外的信息了
直接枚举 s u m ∈ [ 0 , a ] sum \in [0, a] sum[0,a],看看加上 w [ i ] w[i] w[i] 是否符合: s u m + w [ i ] ∈ [ a + 1 , b ] sum + w[i] \in [a + 1, b] sum+w[i][a+1,b] 即可,
如果符合,那么以 i i i最后一个选择的贡献就要加上: d p [ j ] [ s u m ] × 1 n − j dp[j][sum] \times \dfrac{1}{n - j} dp[j][sum]×nj1,后面的系数就代表从 n − j n - j nj 个物品中选出第 i i i 个物品作为最后一个 的概率(位置唯一,分子为 1 1 1

时间复杂度: O ( n 2 a ) O(n^2a) O(n2a)

#include<bits/stdc++.h>
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;

const int INF=0x3f3f3f3f;
const long long INFLL=1e18;

typedef long long ll;

const int N = 505;

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int n, a, b;
    std::cin >> n >> a >> b;
    std::vector<int> w(n + 1);
    std::vector<std::vector<double>> dp(n + 1, std::vector<double>(a + 1));
    dp[0][0] = 1;
    fore(i, 1, n + 1){
        std::cin >> w[i]; //分数
        for(int j = n; j >= 1; --j) //枚举已有牌数
            fore(sum, w[i], a + 1) //枚举分数和
                dp[j][sum] += dp[j - 1][sum - w[i]] * j / (n - j + 1);
    }

    double ans = 0;
    fore(i, 1, n + 1){
        std::vector<std::vector<double>> tmp = dp; //回滚背包
        fore(j, 1, n + 1) //消除这张牌的影响
            fore(sum, w[i], a + 1)
                tmp[j][sum] -= tmp[j - 1][sum - w[i]] * j / (n - j + 1);
        
        fore(j, 0, n) //可以不选或选n-1张牌
            fore(sum, 0, a + 1) //选最后这张牌之前,分数和可能为0或恰好为a
                if(sum + w[i] > a && sum + w[i] <= b)
                    ans += tmp[j][sum] / (n - j); //以这张牌作为最后一行获胜牌
    }

    std::cout << std::fixed << std::setprecision(12) << ans;

    return 0;
}

相关推荐

  1. 2024集训派对day2

    2024-05-10 01:36:06       26 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-05-10 01:36:06       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-05-10 01:36:06       106 阅读
  3. 在Django里面运行非项目文件

    2024-05-10 01:36:06       87 阅读
  4. Python语言-面向对象

    2024-05-10 01:36:06       96 阅读

热门阅读

  1. 前端代码优化

    2024-05-10 01:36:06       36 阅读
  2. SSD存储基本知识

    2024-05-10 01:36:06       32 阅读
  3. C++学习笔记(多线程)

    2024-05-10 01:36:06       31 阅读
  4. Vuex存储数据实例

    2024-05-10 01:36:06       35 阅读
  5. SSH免密登录

    2024-05-10 01:36:06       31 阅读
  6. baxter机械臂校准

    2024-05-10 01:36:06       30 阅读