《挑战程序设计竞赛》2.3 动态规划-优化递推 POJ1742 3046 3181
POJ1742
http://poj.org/problem?id=1742
题意
有n种面额的硬币,面额个数分别为Ai、Ci,求最多能搭配出几种不超过m的金额?
思路
据说这是传说中的男人8题呢,对时间和空间复杂度要求都挺高的。
朴素DP三重循环比较容易想到,但显而易见会TLE。
这里由于dp数组记录的是一个bool值(是否能搭配出某金额),记录的信息较少,因而存在浪费。优化思路是dp[i][j]记录用前i种数加和得到j时第i种数最多能剩余多少个(不能加和得到的情况下为-1)。但二维dp数组的空间复杂度仍然没有降下来,这时候会MLE。
改成一维数组几个AC。
关于思路更详细的说明见《挑战程序设计竞赛》第62-64页。
另外可参见另一篇博客,对于这道题解释的非常清楚:POJ1742详解
代码
Source Code
Problem: 1742 User: liangrx06
Memory: 564K Time: 1907MS
Language: C++ Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 100;
const int M = 100000;
int main(void)
{
int n, m;
int a[N], c[N];
int dp[M+1];
while (scanf("%d%d", &n, &m) != EOF) {
if (!n && !m) break;
for (int i = 0; i < n; i ++)
scanf("%d", &a[i]);
for (int i = 0; i < n; i ++)
scanf("%d", &c[i]);
fill(dp, dp+m+1, -1);
dp[0] = 0;
for (int i = 0; i < n; i ++) {
for (int j = 0; j <= m; j ++) {
if (dp[j] >= 0)
dp[j] = c[i];
else if (j < a[i] || dp[j-a[i]] <= 0)
dp[j] = -1;
else
dp[j] = dp[j-a[i]] - 1;
}
}
int ans = 0;
for (int i = 1; i <= m; i ++) {
if (dp[i] >= 0) ans++;
}
printf("%d\n", ans);
}
return 0;
}
POJ3046
http://poj.org/problem?id=3046
题意
有A只蚂蚁,来自T个家族。同一个家族的蚂蚁长得一样,但是不同家族的蚂蚁牙齿颜色不同。任取n只蚂蚁(S<=n<=B),求能组成几种集合?
还可以理解为:
给出T种数字。每种各有N[i]个,然后用这些数字构成一些序列, 问x长度到y长度的序列有多少种?
思路
动态规划题,dp[i][j] 表示前i种数字构成长度为j的序列有多少种。递推关系为:
dp[i][j] = sigma(dp[i - 1][j - k]) k的范围是0~N[i]
注意到这里的sigma(dp[i - 1][j - k]) 可以用部分和算一下。
然后因为总共的数字个数可能有10W个,有1000种数组,会MLE。所以需要开滚动数组来搞。
复杂度的话 是 O(sigma(num[i] * (T + 1 - i))),最坏是1亿。
代码
Source Code
Problem: 3046 User: liangrx06
Memory: 1028K Time: 125MS
Language: C++ Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int T = 1000;
const int A = T*100;
const int MOD = 1000000;
int main(void)
{
int t, a, s, b;
int fam[T];
cin >> t >> a >> s >> b;
memset(fam, 0, sizeof(fam));
int num;
for (int i = 0; i < a; i ++) {
scanf("%d", &num);
fam[num-1] ++;
}
int dp[2][A+1];
memset(dp, 0, sizeof(dp));
for (int j = 0; j <= b; j ++) {
if (j <= fam[0]) dp[0][j] = 1;
}
for (int i = 1; i < t; i ++) {
for (int j = 0; j <= b; j ++) {
dp[i&1][j] = 0;
for (int k = 0; k <= j && k <= fam[i]; k ++) {
dp[i&1][j] += dp[(i-1)&1][j-k];
}
dp[i&1][j] %= MOD;
}
}
int ans = 0;
for (int j = s; j <= b; j ++)
ans += dp[(t-1)&1][j];
printf("%d\n", ans % MOD);
return 0;
}
POJ3181
http://poj.org/problem?id=3181
题意
输入n,和k,问将n用1到k这k个数字进行拆分,有多少种拆分方法。例如:
n=5,k=3 则有n=3+2,n=3+1+1,n=2+1+1+1,n=2+2+1,n=1+1+1+1+1这5种拆分方法
思路
很明显是完全背包问题,假如用dp[i][j]表示考虑到用数1-i拼接成数字j的拼接方法,可以得到状态转移方程如下:
dp[i][j]=sigma(dp[i-1][j-k*i])(k>=0,且j-k*i>=0)
这个题的答案比较大,超过了long long的表示范围,但其实答案的范围可以大致试出来,两个long long拼起来足够表示。
另外二维数组显然是空间浪费的,用滚动数组处理可以将内存空间降维。甚至可以用一维数组表示。
最后,累加实际上是不必要的,还有更进一步的优化思路:
其实可以转到dp[i][j]的状态有两种,一种是dp[i-1][j]就是不用i这个数字拼接成j这个数字的方法数,另一种是dp[i][j-i]就是用了i这个数字拼接的到j-i的方法数。
那么状态转移方程就可以写成:
dp[i][j]=dp[i-1][j]+dp[i][j-i]
做本题的过程中参考了这篇博文:POJ 3181 Dollar Dayz (完全背包)
代码1(累加DP)
Source Code
Problem: 3181 User: liangrx06
Memory: 264K Time: 47MS
Language: C++ Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int K = 100;
const int N = 1000;
int main(void)
{
int n, k;
cin >> n >> k;
long long INF = 1;
for (int i = 0; i < 18; i++)
INF *= 10;
long long dp[2][N+1][2];
memset(dp, 0, sizeof(dp));
dp[0][0][0] = 1;
dp[0][0][1] = 0;
for (int i = 1; i <= k; i ++) {
for (int j = 0; j <= n; j ++) {
dp[i&1][j][0] = dp[i&1][j][1] = 0;
for (int k = 0; k <= j; k += i) {
dp[i&1][j][0] += dp[(i-1)&1][j-k][0];
dp[i&1][j][1] += dp[(i-1)&1][j-k][1];
if (dp[i&1][j][0] >= INF) {
dp[i&1][j][0] -= INF;
dp[i&1][j][1] += 1;
}
}
}
}
if (dp[k&1][n][1])
printf("%lld", dp[k&1][n][1]);
printf("%lld\n", dp[k&1][n][0]);
return 0;
}
代码2(优化DP,时间复杂度降维)
Source Code
Problem: 3181 User: liangrx06
Memory: 264K Time: 16MS
Language: C++ Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int K = 100;
const int N = 1000;
int main(void)
{
int n, k;
cin >> n >> k;
long long INF = 1;
for (int i = 0; i < 18; i++)
INF *= 10;
long long dp[2][N+1][2];
memset(dp, 0, sizeof(dp));
dp[0][0][0] = 1;
dp[0][0][1] = 0;
for (int i = 1; i <= k; i ++) {
for (int j = 0; j <= n; j ++) {
dp[i&1][j][0] = dp[(i-1)&1][j][0] + ((j-i >= 0) ? dp[i&1][j-i][0] : 0);
dp[i&1][j][1] = dp[(i-1)&1][j][1] + ((j-i >= 0) ? dp[i&1][j-i][1] : 0);
if (dp[i&1][j][0] >= INF) {
dp[i&1][j][0] -= INF;
dp[i&1][j][1] += 1;
}
}
}
if (dp[k&1][n][1])
printf("%lld", dp[k&1][n][1]);
printf("%lld\n", dp[k&1][n][0]);
return 0;
}
《挑战程序设计竞赛》2.3 动态规划-优化递推 POJ1742 3046 3181的更多相关文章
- 挑战程序设计竞赛》P345 观看计划
<挑战程序设计竞赛>P345 观看计划 题意:一周一共有M个单位的时间.一共有N部动画在每周si时 ...
- Aizu 2249Road Construction 单源最短路变形《挑战程序设计竞赛》模板题
King Mercer is the king of ACM kingdom. There are one capital and some cities in his kingdom. Amazin ...
- POJ 2386 Lake Counting 题解《挑战程序设计竞赛》
地址 http://poj.org/problem?id=2386 <挑战程序设计竞赛>习题 题目描述Description Due to recent rains, water has ...
- hdu6078[优化递推过程] 2017多校4
这道题一眼看过去好像和最长公共子序列有点像. 一开始只想到暴力的推法, 令dp[i][j][k]表示 a[i]=b[j](即以ai,bj为结尾的波浪序列的方案数), 且最终状态为k(0,1分别代表下降 ...
- HDU 5863 cjj's string game (矩阵乘法优化递推)
题目大意:用k种字符构建两个长度为n的字符串(每种字符有无限多个),要求对应位置字符相同的连续子串最长长度为m,问方法数. 其中k,n,m是输入,n(1<=n<=1000000000), ...
- poj 3253 Fence Repair 贪心 最小堆 题解《挑战程序设计竞赛》
地址 http://poj.org/problem?id=3253 题解 本题是<挑战程序设计>一书的例题 根据树中描述 所有切割的代价 可以形成一颗二叉树 而最后的代价总和是与子节点和深 ...
- 洛谷 P1464 Function【动态规划(递推)/记忆化搜索(递归)】
题目描述 对于一个递归函数w(a,b,c) 如果a<=0 or b<=0 or c<=0就返回值1. 如果a>20 or b>20 or c>20就返回w(20,2 ...
- 《挑战程序设计竞赛》2.3 动态规划-基础 POJ3176 2229 2385 3616 3280
POJ3176 Cow Bowling 题意 输入一个n层的三角形,第i层有i个数,求从第1层到第n层的所有路线中,权值之和最大的路线. 规定:第i层的某个数只能连线走到第i+1层中与它位置相邻的两个 ...
- 《挑战程序设计竞赛》2.3 动态规划-进阶 POJ1065 1631 3666 2392 2184(5)
POJ1065: Description There is a pile of n wooden sticks. The length and weight of each stick are kno ...
随机推荐
- 用Jquery控制文本框只能输入数字和字母及jquery自定义方法$.fn
封装成onlyNum(),onlyAlpha()和onlyNumAlpha()3个Jquery扩展方法,方便复用,由于里面一些JS代码涉及到了"禁用输入法,获取剪切板的内容",而& ...
- Spring Boot(七):spring boot测试介绍
首先maven要引入spring-boot-starter-test这个包. 先看一段代码 @RunWith(SpringRunner.class) @SpringBootTest(webEnviro ...
- 启动tomcat的时候,报Neither the JAVA_HOME nor the JRE_HOME environment variable is defined At least one of these environment variable is needed to run this program 的解决方案
解决方法,如下所示: 先看Tomcat的startup.bat,它调用了catalina.bat,而catalina.bat则调用了setclasspath.bat.只要在setclasspath.b ...
- 使用心跳机制实现CS架构下多客户端的在线状态实时更新以及掉线自动重连
此文讲述的内容是一个实际项目开发中的一部分内容,笔者将亲身经历写成文章. [背景] 现需要实现这样的功能:有多个客户端连着同一个服务器.服务器和客户端之间需要“互相”知道彼此的连接状态.比如在某一时刻 ...
- PHP——做服务
xml的写法和特点 <?xml version='1.0' encoding='utf-8'?><Info><code>c001</code><n ...
- Zookeeper的结构和命令
1. Zookeeper的特性 1.Zookeeper:一个leader,多个follower组成的集群. 2.全局数据一致:每个server保存一份相同的数据副本,client无论连接到哪个serv ...
- 使用AccessibilityService实现微信自己主动抢红包
近期要实现微信自己主动抢红包的功能.使用AccessibilityService来开发,这里主要写一下逻辑以及注意点. 注意点 1.搜索keyword 我们实现某个功能比方点击等须要找到相应的对象然后 ...
- 一行python打印乘法表
一行代码打印乘法表 >>> print '\n'.join([' '.join(['%s*%s=%-2s' %(y,x,x*y) for y in range(1,x+1)]) fo ...
- pl/sql 实例精解 04
本章主要讨论, IF 语句的应用. 1: if condition1 then 2: statement1 3: elsif condition2 then 4: statement2 5: else ...
- WPF 开源项目 【watcher】 守望者,一款监控,统计,分析你每天在自己电脑上究竟干了什么的软件
时隔多年(两年),天天沉迷写PHP的我在连续加薪了逐渐发现自己不怎么写代码了. 甚至有一天我发现我连IDE 都没有打开,实在是太堕落了 为了及时悬崖勒马,回头是岸.为了鼓励自己专心写代码,我决定写一款 ...