随笔- 25  评论- 4  文章- 0 

HDU4815 Little Tiger vs. Deep Monkey——0-1背包

题目描述

对于n道题目,每道题目有一个分值,答对加分,答错不得分,你要和一个叫深猴的比赛,题目你可以假设成判断题(不是对就是错),深猴对于所有的题目都是随机选择一个答案,而你是有脑子的,求为了不输掉比赛(平局或你获胜)的可能性至少为p时你至少需要得到多少分,有t组数据,每次输入两行,第一行为n,p(有n道题目,n<=40, 不会输的可能性为p,0.0<=p<=1.0),第二行输入n个1~1000的整数,代表这n道题分别答对能获得的分数

样例输入

1
3 0.5
1 2 3

样例输出

3

题目分析

首先对于这n道题目,深猴每次不是√就是×,那么答完所有的题目它的分数有2^n个结果(但是这其中可能会重复,比如三道题每题一分1 0 0和0 0 1其实得到的分数是一样的),而对于我们而言,我需要求出这n个题目自己得到每一种可能的分数的可能性,然后按分数从小到大将,这得到这些分数的概率相加,直到有一个分数m时,前面相加的概率和>=p,则在分数大于等于m时,确保我有至少p的概率不会输掉比赛

错误示例

我第一次做这道题目的时候想的通过递归计算出做出n个选择之后我可以得到的每一个分数的种数存放在a数组中,a[i]代表总分为i的种数,而又建立了一个辅助数组b[i]存放分数比i小的种数有多少种,很显然b[i] = b[i-1] + a[i-1](比i小的数量等于比i-1小的数量加上a[i-1]的数量),最后也是从小到大将每一种得分的可能性相加直到大于等于p时的分数则是答案,是一种前缀和的思想,但是会超时

 1 #include<iostream>
 2 #include<stdio.h>
 3 #include<string.h>
 4 #include<math.h>
 5 using namespace std;
 6 
 7 int k[45];
 8 int a[40005];
 9 int b[40005];
10 int n;
11 double p;
12 
13 void dfs(int i, int flag, int sum){            //第i个 选or不选 前一个状态的和 
14     if(i > n){            //只有当所有的n个都作出了选择之后才能算是一种选择方案 
15         a[sum]++;
16         return; 
17     }
18     if(flag == 1){            //
19         int next_sum = sum + k[i];
20 //        a[next_sum]++;
21         dfs(i+1, 1, next_sum);
22         dfs(i+1, 0, next_sum);
23     }else{                    //不选 
24         int next_sum = sum;
25 //        a[next_sum]++;
26         dfs(i+1, 1, next_sum);
27         dfs(i+1, 0, next_sum);
28     }
29 }
30 
31 void run(){
32     //下一个的下标 选or不选 目前为止的和 
33     dfs(1, 1, 0);
34     dfs(1, 0, 0); 
35 }
36 
37 void pre(){
38     b[1] = 1;    
39     int end = n*1000;                
40     for(int i = 2; i <= end; i++){    //b[i]存放比i小的取法的数量 
41         b[i] = b[i-1] + a[i-1];        
42     }
43 }
44 
45 void judge(){
46     double m = 1;
47     for(int i = 1; i <= n; i++){
48         m *= 2;
49     }    
50     m *= p;
51     long long x = ceil(m);
52     int ans;
53     int end = n*1000;        
54     for(int i = 1; i <= end; i++){
55         if(b[i] >= x){
56             ans = i;
57             break;
58         } 
59     } 
60     printf("%d\n", ans);
61 }
62 
63 int main(){
64     int t;
65     scanf("%d", &t);
66     while(t--){
67         scanf("%d%lf", &n, &p);
68         memset(a, 0, sizeof(a));
69         memset(b, 0, sizeof(b));
70         for(int i = 1; i <= n; i++) scanf("%d", &k[i]);
71         run();
72         pre();
73         judge();
74     }
75     return 0;
76 } 

 

正确思路

本题可以用到动态规划,0-1背包的思想,a[i]存放这n个题目的分数,dp[i][j]存放前i题,得到j分数的种数,而我们很显然可以想到,对于分数j,如果dp[i][j]可以得到,则他一定是dp[i-1][j-a[i]](第i题的分数取的种数) + dp[i-1][j](第i题的分数不取的种数)的基础上来的(对于第i题而言取的话,j == j - a[i] + a[i],不取的话就是i-1个问题,分数为j的种数,是0-1背包的问题),所以我们也可以对此用一个一维数组进行优化,dp[x]存放分数为x的种数,而初始化时dp[0] == 1,因为可以理解成前0题,得到0分的种数为1

正确代码

 1 #include<iostream>
 2 #include<stdio.h>
 3 #include<math.h>
 4 #include<string.h>
 5 using namespace std;
 6 
 7 const int N = 40005;
 8 int a[45];
 9 double dp[N];
10 int n;
11 double p;
12 
13 int main(){
14     int t;
15     scanf("%d", &t);
16     while(t--){
17         scanf("%d%lf", &n, &p);
18         int sum = 0;
19         for(int i = 1;i <= n; i++){        //sum统计最大可以得到的分数 
20             scanf("%d", &a[i]);
21             sum += a[i];    
22         } 
23         memset(dp, 0, sizeof(dp));
24         dp[0] = 1;                //前0题,得到0分的次数位1 
25         for(int i = 1; i <= n; i++){            //类似于0-1背包的两个循环 
26             for(int j = sum; j >= a[i]; j--){    
27                 dp[j] += dp[j-a[i]];            //核心步骤,对于dp[j]而言,得分为j的种数是前i-1个时得分为j-a[i] 也就是取第i题,加上前i-1个时得分为j的种数 也就是不取第i题
28             }
29         } 
30         double m = pow(2,n);        //统计所有的深猴的得分个数 
31         int ans = 0;
32         double ssum = 0;
33         for(int i = 0; i <= sum; i++){
34             dp[i] /= m;                //从小到大将概率累加直到大于等于p时的分数i就是答案,注意0也是一个得分 
35             ssum += dp[i];
36             if(ssum >= p){
37                 ans = i;
38                 break;
39             }
40         } 
41         printf("%d\n", ans);
42     }
43     return 0;
44 }

 

posted on 2019-08-18 16:49 乌克兰大野猪 阅读(...) 评论(...) 编辑 收藏