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 }