简单dp

P1616 疯狂的采药

题目描述


此题和原题的不同点:

1.每种草药可以无限制地疯狂采摘。

2.药的种类眼花缭乱,采药时间好长好长啊!师傅等得菊花都谢了!

输入输出格式

输入格式:

输入第一行有两个整数T(1 <= T <= 100000)和M(1 <= M <= 10000),用一个空格隔开,T代表总共能够用来采药的时间,M代表山洞里的草药的数目。接下来的M行每行包括两个在1到10000之间(包括1和10000)的整数,分别表示采摘某种草药的时间和这种草药的价值。

输出格式:

输出一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。

输入输出样例

输入样例#1: 复制
70 3
71 100
69 1
1 2
输出样例#1: 复制
140

说明

对于30%的数据,M <= 1000;

对于全部的数据,M <= 10000,且M*T<10000000(别数了,7个0)。

这题即无限背包,与01背包区别在于可以取无数次。

刚开始看M和T 的范围不对,但后来又限制了M和T的范围,所以这题是可做的。

只要把01背包的第二重循环的逆序改成正序,那么物品就可以取无数次。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<stack>
using namespace std;
const int MAXN = 100000;
const int MAXM = 10000;
typedef long long LL;
typedef double DB;
inline int get(){
    char c;
    while((c = getchar()) < '0' || c > '9');
    int cnt = c - '0';
    while((c = getchar()) >= '0' && c <= '9') cnt = cnt * 10 + c - '0';
    return cnt;
}
LL f[MAXN + 10];
int w[MAXM + 10];
int t[MAXM + 10];
int T,M;
int main(){
/*	#ifdef lwy
        freopen(".txt","r",stdin);
    #else
        freopen(".in","r",stdin);
        freopen(".out","w",stdout);
    #endif*/ 
    T = get(); M = get();
    for(int i = 1; i <= M; i ++){
        t[i] = get(); w[i] = get();
    } 
    f[0] = 0;
    for(int i = 1; i <= M; i ++){
        for(int j = t[i]; j <= T; j ++){
            f[j] = max(f[j],f[j - t[i]] + w[i]); 
        }
    }
    printf("%lld",f[T]);
    return 0;
}



P1049 装箱问题

题目描述

有一个箱子容量为V(正整数,0<=V<=20000),同时有n个物品(0<n<=30,每个物品有一个体积(正整数)。

要求n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。

输入输出格式

输入格式:

一个整数,表示箱子容量

一个整数,表示有n个物品

接下来n行,分别表示这n 个物品的各自体积

输出格式:

一个整数,表示箱子剩余空间。

输入输出样例

输入样例#1: 复制
24
6
8
3
12
7
9
7
输出样例#1: 复制
0

说明

NOIp2001普及组 第4题

用f【i】 表示能取到的最大的体积,因为体积是有限的,只要对每个物品,把当前能取到的体积再加上物品的体积,这样就可以穷举出体积的所有情况,取最大的体积即可。

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
bool f[20010];
int a[40];
int N,V;
int main(){
    scanf("%d %d",&V,&N); 
    for(int i = 1; i <= N; i ++){
        scanf("%d",&a[i]);
    }
    memset(f,false,sizeof(f));
    f[0] = true;
    for(int i = 1; i <= N; i ++){
        for(int j = V; j >= 0; j --){
            if(f[j] && j + a[i] <= V) f[j + a[i]] = true;
        }
    }
    int ans = 0;
    for(int i = 1; i <= V; i ++){
        if(f[i]) ans = i;
    }
    printf("%d",V - ans);
    return 0;
}


这题也可以看作是背包问题,只是物品的体积同时也是它的价值,求最大价值,就像01背包那样就行了。


P1880 石子合并

题目描述

在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.

输入输出格式

输入格式:

数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.

输出格式:

输出共2行,第1行为最小得分,第2行为最大得分.

输入输出样例

输入样例#1: 复制
4
4 5 9 4
输出样例#1: 复制
43
54

以最大值为例

考虑到n很小,可以想到用f【i】【j】表示从 i 合并到 j 的最大值,那么可以进一步转化为子问题,求各个分区间的最大值也可以这样转化,而f【i】【i】的值为0。

不难想到 f【i】【j】 = max (f【i + k】+ f【i + k + 1】【j】)+ a【j】 - a【i - 1】

这里a数组用于求区间石子总数。

给出的程序 j 的含义是 区间长度 - 1。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<stack>
using namespace std;
const int MAXN = 200;
typedef long long LL;
typedef double DB;
inline int get(){
    char c;
    while((c = getchar()) < '0' || c > '9');
    int cnt = c - '0';
    while((c = getchar()) >= '0' && c <= '9') cnt = cnt * 10 + c - '0';
    return cnt;
}
int N;
int a[MAXN + 10];
int maxa[MAXN + 10][MAXN + 10],mina[MAXN + 10][MAXN + 10];
int maxans,minans;
int main(){
    #ifdef lwy
        freopen("1.txt","r",stdin);
/*	#else
        freopen(".in","r",stdin);
        freopen(".out","w",stdout);*/ 
    #endif
    memset(maxa,0,sizeof(maxa));
    memset(mina,127,sizeof(mina));
    N = get();
    for(int i = 1; i <= N; i ++){
        a[i + N] = a[i] = get(); 
        mina[i + N][i + N] = mina[i][i] = 0;
    }
    for(int i = 2; i <= 2 * N; i ++) a[i] += a[i - 1];
    for(int j = 1; j < N; j ++){
        for(int i = 1; i + j <= 2 * N; i ++){
            for(int k = 0; k < j; k ++){
                maxa[i][i + j] = max(maxa[i][i + j],maxa[i][i + k] + maxa[i + k + 1][i + j] + a[i + j] - a[i -1]);
                mina[i][i + j] = min(mina[i][i + j],mina[i][i + k] + mina[i + k + 1][i + j] + a[i + j] - a[i -1]); 
            }
        }
    }
    maxans = maxa[1][N]; minans = mina[1][N];
    for(int i = 2; i <= N; i ++){
        maxans = max(maxans,maxa[i][i + N - 1]);
        minans = min(minans,mina[i][i + N - 1]);
    }
    printf("%d\n%d",minans,maxans);
    return 0;
}



P1541 乌龟棋

题目背景

小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。

题目描述

乌龟棋的棋盘是一行N个格子,每个格子上一个分数(非负整数)。棋盘第1格是唯一的起点,第N格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。

乌龟棋中M张爬行卡片,分成4种不同的类型(M张卡片中不一定包含所有4种类型的卡片,见样例),每种类型的卡片上分别标有1、2、3、4四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数。游戏中,玩家每次需要从所有的爬行卡片中选择一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。

游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到该格子相应的分数。玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的分数总和。

很明显,用不同的爬行卡片使用顺序会使得最终游戏的得分不同,小明想要找到一种卡片使用顺序使得最终游戏得分最多。

现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到多少分吗?

输入输出格式

输入格式:

输入文件的每行中两个数之间用一个空格隔开。

第1行2个正整数N和M,分别表示棋盘格子数和爬行卡片数。

第2行N个非负整数,a1a2……aN,其中ai表示棋盘第i个格子上的分数。

第3行M个整数,b1b2……bM,表示M张爬行卡片上的数字。

输入数据保证到达终点时刚好用光M张爬行卡片。

输出格式:

输出只有1行,1个整数,表示小明最多能得到的分数。

输入输出样例

输入样例#1: 复制
9 5
6 10 14 2 8 8 18 5 17
1 3 1 2 1
输出样例#1: 复制
73

说明

每个测试点1s

小明使用爬行卡片顺序为1,1,3,1,2,得到的分数为6+10+14+8+18+17=73。注意,由于起点是1,所以自动获得第1格的分数6。

对于30%的数据有1≤N≤30,1≤M≤12。

对于50%的数据有1≤N≤120,1≤M≤50,且4种爬行卡片,每种卡片的张数不会超过20。

对于100%的数据有1≤N≤350,1≤M≤120,且4种爬行卡片,每种卡片的张数不会超过40;0≤ai≤100,1≤i≤N;1≤bi≤4,1≤i≤M。


显然局部最优不等于总体最优,我们考虑dp
因为最多只有40张牌,我们考虑用f【i】【j】【k】【t】表示用 i 张 1,j 张 2,k 张 3,t 张 4 所能得到的最大值,通过 i j k t 我们可以得出当前所在的位置。
不难想到这四种转移方式。
	int len = i + j * 2 + k * 3 + t * 4;
	int& p = f[i][j][k][t]; 
	if(i > 0) p = max(p,f[i - 1][j][k][t]);
	if(j > 0) p = max(p,f[i][j - 1][k][t]);
	if(k > 0) p = max(p,f[i][j][k - 1][t]);
	if(t > 0) p = max(p,f[i][j][k][t - 1]);
	p += a[len];


#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<stack>
using namespace std;
const int MAXN = 0;
typedef long long LL;
typedef double DB;
int f[50][50][50][50]; 
int num[10];
int N,M;
int a[360];
inline int get(){
	char c;
	while((c = getchar()) < '0' || c > '9');
	int cnt = c - '0';
	while((c = getchar()) >= '0' && c <= '9') cnt = cnt * 10 + c - '0';
	return cnt;
}
int main(){
	#ifdef lwy
		freopen("1.txt","r",stdin);
	/*#else
		freopen(".in","r",stdin);
		freopen(".out","w",stdout);*/
	#endif
	N = get(); M = get();
	for(int i = 0; i < N; i ++){
		a[i] = get();
	}
	memset(num,0,sizeof(num));
	for(int i = 1; i <= M; i ++){
		int k; k = get();
		num[k] ++;
	}
	for(int i = 0; i <= num[1]; i ++){
		for(int j = 0; j <= num[2]; j ++){
			for(int k = 0; k <= num[3]; k ++){
				for(int t = 0; t <= num[4]; t ++){
					int len = i + j * 2 + k * 3 + t * 4;
					int& p = f[i][j][k][t]; 
					if(i > 0) p = max(p,f[i - 1][j][k][t]);
					if(j > 0) p = max(p,f[i][j - 1][k][t]);
					if(k > 0) p = max(p,f[i][j][k - 1][t]);
					if(t > 0) p = max(p,f[i][j][k][t - 1]);
					p += a[len];
				}
			}
		}
	}
	printf("%d",f[num[1]][num[2]][num[3]][num[4]]);
	return 0;
}






  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值