动态规划题目——背包

1. 01背包

[HDOJ 2955.Robberies]

HDOJ 2955.Robberies

题目大意:有一个名叫Roy窃贼去抢银行,他想抢到更多的钱并且不被逮捕。

输入:
共T组数据数据。
每组数据第一行有两个数P和N,代表Roy被捕的总概率和银行的个数。
接下来N行每行两个数Pj和Mj代表被捕的概率和银行所存的钱数。
0 < T <= 100
0.0 <= P <= 1.0
0 < N <= 100
0 < Mj <= 100
0.0 <= Pj <= 1.0

输出:
能获得钱数的最大值。

分析:很明显的一个01背包问题,我们将P=1-P(不被捕的概率),p[i]=1-p[i](同理),dp[i]代表获得i价值时不被被逮捕的概率,那么就很容易得到状态转移方程为dp[i] = max ( dp[i] ,dp[i-m[i]]*p[i] ),且初始状态dp[0]=1(不偷就不会被捕)。
注意概率是相乘而不是相加

#include <iostream>
#include <cstring>
using namespace std;

const int maxn=1e6+10;

int t;
double p[maxn],dp[maxn];
int sum,v[maxn];

int main()
{
    double P;
    int n;
    cin >> t;
    while(t--)
    {
        cin >> P >> n;
        P=1-P;
        sum=0;
        for(int i=1;i<=n;i++) 
        {
            cin >> v[i] >> p[i];
            p[i]=1-p[i];
            sum+=v[i];
        }
        memset(dp,0.0,sizeof(dp));
        dp[0]=1;
        for(int i=1;i<=n;i++)
        {
            for(int j=sum;j>=v[i];j--)
            {
                dp[j]=max(dp[j],dp[j-v[i]]*p[i]);
            }
        }
        for(int i=sum;i>=0;i--)
        {
            if(dp[i]>=P)
            {
                cout << i << endl;
                break;
            }
        }
    }
    return 0;
}

[HDOJ 1203.I NEED A OFFER!]

HDOJ 1203.I NEED A OFFER!

Problem Description
Speakless很早就想出国,现在他已经考完了所有需要的考试,准备了所有要准备的材料,于是,便需要去申请学校了。要申请国外的任何大学,你都要交纳一定的申请费用,这可是很惊人的。Speakless没有多少钱,总共只攒了n万美元。他将在m个学校中选择若干的(当然要在他的经济承受范围内)。每个学校都有不同的申请费用a(万美元),并且Speakless估计了他得到这个学校offer的可能性b。不同学校之间是否得到offer不会互相影响。“I NEED A OFFER”,他大叫一声。帮帮这个可怜的人吧,帮助他计算一下,他可以收到至少一份offer的最大概率。(如果Speakless选择了多个学校,得到任意一个学校的offer都可以)。

Input
输入有若干组数据,每组数据的第一行有两个正整数n,m(0<=n<=10000,0<=m<=10000)
后面的m行,每行都有两个数据ai(整型),bi(实型)分别表示第i个学校的申请费用和可能拿到offer的概率。
输入的最后有两个0。

Output
每组数据都对应一个输出,表示Speakless可能得到至少一份offer的最大概率。用百分数表示,精确到小数点后一位。

分析:同上,将概率转换求最小出不去的概率

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <bitset>
#include <map>
#include <utility>
using namespace std;

const int maxn=1e6+10;

double p[maxn],dp[maxn];
int v[maxn];

int main()
{
    int n,k;
    while(scanf("%d%d",&n,&k)==2)
    {
    	if(n==0 && k==0) break;
        for(int i=1;i<=k;i++) 
        {
            scanf("%d%lf",&v[i],&p[i]);
            p[i]=1-p[i];
        }
        for(int i=0;i<=n;i++) dp[i]=1;
        for(int i=1;i<=k;i++)
        {
            for(int j=n;j>=v[i];j--)
            {
                dp[j]=min(dp[j],dp[j-v[i]]*p[i]);
            }
        }
        printf("%.1lf%%\n",(1.0-dp[n])*100.0);
    }
    return 0;
}

[HDOJ 1864.最大报销额]

HDOJ 1864.最大报销额

现有一笔经费可以报销一定额度的发票。允许报销的发票类型包括买图书(A类)、文具(B类)、差旅(C类),要求每张发票的总额不得超过1000元,每张发票上,单项物品的价值不得超过600元。现请你编写程序,在给出的一堆发票中找出可以报销的、不超过给定额度的最大报销额。

Input
测试输入包含若干测试用例。每个测试用例的第1行包含两个正数 Q 和 N,其中 Q 是给定的报销额度,N(<=30)是发票张数。随后是 N 行输入,每行的格式为:m Type_1:price_1 Type_2:price_2 … Type_m:price_m
其中正整数 m 是这张发票上所开物品的件数,Type_i 和 price_i 是第 i 项物品的种类和价值。物品种类用一个大写英文字母表示。当N为0时,全部输入结束,相应的结果不要输出。

Output
对每个测试用例输出1行,即可以报销的最大数额,精确到小数点后2位。

分析:01背包板子,注意数据处理
单项物品是指每张发票A(或B或C)类物品之和,不是其中一项A的价值。

#include <iostream>
#include <cstring>
using namespace std;

const int maxn=2e6+10;

string s;
int dp[maxn],sum[maxn];

int main()
{
	double d_Q;
	int n;
	while((cin >> d_Q >> n) && n)
	{
		memset(sum,0,sizeof(sum));
		memset(dp,0,sizeof(dp));
		
		int tot=0;
		int Q=(int)(d_Q*100);
		int flag=1;
		
		for(int i=1;i<=n;i++)
		{
			int k;
			double pa=0,pb=0,pc=0;
			cin >> k;
			for(int j=1;j<=k;j++)
			{
				char type;
				double price;
				scanf(" %c:%lf",&type,&price);
                if(type=='A') pa+=price;
                else if(type=='B') pb+=price;
                else if(type=='C') pc+=price;
                else flag=0;
			}
			if(pa+pb+pc<=1000 && pa<=600 && pb<=600 && pc<=600 && flag) sum[++tot]=(int)((pa+pb+pc)*100);
		}
		
		for(int i=1;i<=tot;i++)
		{
			for(int j=Q;j>=sum[i];j--)
			{
				dp[j]=max(dp[j],dp[j-sum[i]]+sum[i]);
			}
		}
		
		printf("%.2lf\n",dp[Q]/100.0);
	}
}

2. 完全背包

[HDOJ 2159.FATE]

HDOJ 2159.FATE

Problem Description
最近xhd正在玩一款叫做FATE的游戏,为了得到极品装备,xhd在不停的杀怪做任务。久而久之xhd开始对杀怪产生的厌恶感,但又不得不通过杀怪来升完这最后一级。现在的问题是,xhd升掉最后一级还需n的经验值,xhd还留有m的忍耐度,每杀一个怪xhd会得到相应的经验,并减掉相应的忍耐度。当忍耐度降到0或者0以下时,xhd就不会玩这游戏。xhd还说了他最多只杀s只怪。请问他能升掉这最后一级吗?

Input
输入数据有多组,对于每组数据第一行输入n,m,k,s(0 < n,m,k,s < 100)四个正整数。分别表示还需的经验值,保留的忍耐度,怪的种数和最多的杀怪数。接下来输入k行数据。每行数据输入两个正整数a,b(0 < a,b < 20);分别表示杀掉一只这种怪xhd会得到的经验值和会减掉的忍耐度。(每种怪都有无数个)

Output
输出升完这级还能保留的最大忍耐度,如果无法升完这级输出-1。

分析:先将每一类怪物S进行拆分成二进制数储存,保证能组成S以内的任何怪物数。之后做一个01背包并多一维记录杀掉的怪物个数dp[ j ][ k ] = max ( dp[ j ][ k ] , dp[ j - 1 ][ k - M[ i ]. patient ] + M[ i ]. experience )。如果说dp[ S ][ P ] < 0,说明杀够S个怪物也不能升级;如果能升级,再把dp数组扫一遍找出能够升级经验中耐心消耗的最小值。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <cstdio>
using namespace std;

const int maxn=1e6+10;

struct monster
{
	int experience;
	int patient;
	int k;
}M[maxn];

int dp[110][110];
int tot=0;

int main()
{
	int E,P,n,S;
	while(cin >> E >> P >> n >> S)
	{
		tot=0;
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=n;i++)
		{
			int e,p;
			cin >> e >> p;
			int tmp=1;
			++tot;
			M[tot].experience=e;
			M[tot].patient=p;
			M[tot].k=tmp;
			while(tmp<=S)
			{
				tmp*=2;
				++tot;
				M[tot].experience=e;
				M[tot].patient=p;
				M[tot].k=tmp;
			}
		}
		for(int i=1;i<=tot;i++)
		{
			for(int j=1;j<=S;j++)
			{
				for(int k=M[i].patient;k<=P;k++)
				{
					dp[j][k]=max(dp[j][k],dp[j-1][k-M[i].patient]+M[i].experience);
				}
			}
		}
		if(dp[S][P]<E) cout << -1 << endl;
		else
		{
			int MIN=P;
			for(int i=1;i<=S;i++)
			{
				for(int j=P;j>=0;j--)
				{
					if(dp[i][j]>=E && MIN>j)
					{
						MIN=j;
					}
				}
			}
			cout << P-MIN << endl;
		}
	}
}

3. 多重背包

[HDOJ 1171.Big Event in HDU]

HDOJ 1171.Big Event in HDU

有 n 类物品,每类物品有 m 个,价值为 V ,将它们分为价值相近的A,B两堆并输出,要保证A>=B。

分析:因为数据范围较小可以将 m 个物品拆成单独的,之后进行一个总价值为V/2的01背包就行了。

#include <iostream>
#include <algorithm>
using namespace std;

const int maxn=1e6+10;
const int INF=0x3f3f3f3f;

int main()
{
	int n;
    while(cin >> n)
    {
        if(n<0) break;
        int *w = new int[maxn];
        int sum=0;
        int tot=0;
        int val,k;
        for(int i=1;i<=n;i++)
        {
            cin >> val >> k;
            sum+=k*val;
            while(k--) w[++tot]=val;
        }
        int V=sum/2;
        int *dp = new int[maxn];
        for(int i=1;i<=tot;i++)
        {
            for(int j=V;j>=w[i];j--)
            {
                dp[j]=max(dp[j],dp[j-w[i]]+w[i]);
            }
        }
        cout << sum-dp[V] << " " << dp[V] << endl;
        delete w;
        delete dp;
    }
}

4.二进制优化

[HDOJ 2844.Coins]

HDOJ 2844.Coins

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn=1e6+10;

int a[105],A[maxn];
int c[105],C[maxn];
int dp[maxn];

int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m) && n && m)
    {
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        for(int i=1;i<=n;i++) scanf("%d",&c[i]);
        int tot = 0;
        for(int i=1;i<=n;i++)
        {
            int tmp=1;
            while(c[i]>=tmp)
            {
                tot++;
                A[tot]=a[i]*tmp;
                C[tot]=tmp;
                c[i]-=tmp;
                tmp*=2;
            }
            if(c[i]>0)
            {
                tot++;
                A[tot]=a[i]*c[i];
                C[tot]=c[i];
            }
        }
        dp[0]=1;
        for(int i=1;i<=tot;i++)
        {
            for(int j=m;j>=A[i];j--)
            {
                if(dp[j-A[i]]) dp[j]=1;
            }
        }
        int ans = 0;
        for(int i=1;i<=m;i++)
        {
            if(dp[i]) ans++;
        }
        printf("%d\n",ans);
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值