动态规划与搜索练习(备战CSP)

这是我搜集到的一些动态规划和搜索的练习题

搜索小练习
动态规划小练习
祝你CSP拿到 一等奖 一等奖 ^{一等奖}_{一等奖} 一等奖一等奖!


这是解析

没有排版,没添加图片,很乱,还没整理

动态规划

一、双子序列最大和

由于两个子序列不重叠,显然的这两个子序列之间一定有一个断点。要求两个子序列之和最大值,可以枚举断点的位置,对比每个断点下左序列和右序列的最大值之和,最大的即为答案。
接下来该怎么求解每一个左序列的最大值和右序列的最大值呢?在这样的数据规模下必然是已经打出了两个序列在每个断点处的最大值的一张表,每次掉用只花常数时间。求解左序列在每个断点的最大值这个问题,很容易想到用dp思想求解单序列最大值,那么常规操作:定义状态 f( i ) 为以序列第 i 个数为结尾的序列最大值。
不过上述步骤还没得出左序列在每个断点处最大值的表,它得到的是以断点左端点为结尾的左序列的最大值,而在实际情况中,左序列最大值不一定以断点左端为结尾,所以需要更新这个数组 f ,用 f[i] = max(f[i], f[i - 1]) 这样一个语句就可以实现更新(其实这个操作就像求解单序列最大值最后需要遍历一遍数组找出最大值一样,因为真正的最大序列不一定以最后一个数为结尾)。
对于右序列,只需倒着操作就可实现。

二、小猪购物

见NOIP-2018-提高组初赛-23-小猪购物.pdfNOIP-2018-提高组初赛-23 小猪购买N 件商品问题详解
作者:严老师
完善程序。
一只小猪要买N 件物品(N 不超过1000)。
它要买的所有物品在两家商店里都有卖。第i 件物品在第一家商店的价格是
a[i] ,在第二家商店的价格是b[i] ,两个价格都不小于0 且不超过10000。
如果在第一家商店买的物品的总额不少于50000,那么在第一家店买的物品都
可以打95 折(价格变为原来的0.95 倍)。
求小猪买齐所有物品所需最少的总额。
输入:第一行一个数N。接下来N 行,每行两个数。第i 行的两个数分别代表
a[i],b[i]。
输出:输出一行一个数,表示最少需要的总额,保留两位小数。
试补全程序。
#include
#include
using namespace std;
const int Inf = 1000000000;
const int threshold = 50000;
const int maxn = 1000;
int n, a[maxn], b[maxn];
bool put_a[maxn];
int total_a, total_b;
double ans;
int f[threshold];
int main() {
scanf(“%d”, &n);
total_a = total_b = 0;
for (int i = 0; i < n; ++i) {
scanf(“%d%d”, a + i, b + i);
if (a[i] <= b[i]) total_a += a[i];
else total_b += b[i];
}
ans = total_a + total_b;
total_a = total_b = 0;
for (int i = 0; i < n; ++i) {
if ( ① ) {
put_a[i] = true;
total_a += a[i];
} else {
put_a[i] = false;
total_b += b[i];
}
}i
f ( ② ) {
printf(“%.2f”, total_a * 0.95 + total_b);
return 0;
}f
[0] = 0;
for (int i = 1; i < threshold; ++i)
f[i] = Inf;
int total_b_prefix = 0;
for (int i = 0; i < n; ++i)
if (!put_a[i]) {
total_b_prefix += b[i];
for (int j = threshold - 1; j >= 0; --j) {
if ( ③ >= threshold && f[j] != Inf)
ans = min(ans, (total_a + j + a[i]) * 0.95 + ④ );
f[j] = min(f[j] + b[i], j >= a[i] ? ⑤ : Inf);
}
}
printf(“%.2f”, ans);
return 0;
}
此题考查了贪心算法,动态规划-背包问题相关知识。
提示:对于完善程序题,建议先阅读程序,了解程序大致的思路,不要自己直接
读题后自行思考解决方法,除非是自己非常熟悉的题,否则,考试时建议直接读
程序,快速入手,节省时间,特别是对于题目给出的问题比较陌生,一下子想不
到好的方法的函数,读程序能发现一些常见的算法讨论模板,最后对于实在想不
出来的地方,可以根据代码的对称结构,前后语句,猜测出来。
以本题为例,首先读懂题意,小猪买齐N 件商品,每件商品仅有两种选
择,要么在第一家商店(a 店)买,要么在第二家店(b 店)购买,两家价格不
同。其中a 店有优惠活动,若购买总额达到50000,则在该店购买的所有物品
都可以打95 折。求小猪买齐N 件商品花费总额最少是多少。
首先拿到题,既然要想总花费最少,第一感觉是贪心法,假设能活动a
店折扣,然后选便宜的买。按这样的假设选购后,再检查a 店的购买总额是否确
实能满足打折条件,若能满足,则假设成立,直接得到最优解,若不满足,则需
要再具体分析。
这是一个最初的想法,但显然还不能达到代码实现的程度,然后结合题
目的代码看,很明显。35~37 行有一个判断,然后直接输出结果,return 了,
表示找到最优解了,是满足某种条件下的最优解。肯定是假设a 店购买总额达到
N 情况下的解。a 店的每件物品价格变成原来的0.95 倍,在此基础上将a 店每
件物品打折后的价格与b 店比较,取更小的即可。所以空1 和空2 很明确了。
接下来的难点在于当前贪心法的假设不成立,也就是total_a<N,怎么
办?
那就试着将之前选择在b 店购买的物品,重新调整为在a 店购买,朝着
把a 店的购买总额凑足N 看看。
这样将面临3 个问题:
1、total_a=0 的情况
2、是否一定能保证能调整到a 店的购买总额凑足N?
3、假定能调整到满足优惠条件,怎样调整最好?
先看第1 个问题,若total_a=0,表示a 店打折之后所有物品还是都比
b 店贵,a 店的打折没意义了,直接按原价谁便宜选哪家,这种情况下,结果显
然就是全在b 店买。所以后面要继续分析的情况是0<total_a<N 的情况。
再看第2 个问题,显然不一定能保证,极端情况就是将之前选择在b 店
购买的物品,全部调整为在a 店购买,total_a 依然达不到N,那就可以同样无
视a 店折扣了,直接按原价选便宜的,跟问题1 的结论一样。
这对应于代码18-24 行,不考虑优惠,直接按便宜的挑选,得到的ans
作为最优解的初值,后面再判断是否有更优的解来更新ans。
再看第3 个问题,这个问题是整个问题的关键之处,如图所示分界线右
侧的物品,可以任选几件物品调整为在a 店购买,假定能调整到使之满足优惠条
件,肯定是可能有多种调整方法的。
一个大前提就是最终的购买总额要尽量小,因为调整是将0.95a[i]>b[i]
的物品,调整为在a 店购买,所以应该是以尽量少的调整,使total_a 恰好增加
到N,同时使得分到b 店的购买额也尽量小。
借用01 背包的思路,定义子问题状态f[i][j],其表示,对上图分界线右
侧满足0.95a[i]>b[i]的m 件物品集合,命名为E(i),其前i(1<=i<=n)件物品,
经过重新选购后,分到a 店的购买额恰好为j 时,分到b 店的最小购买额。
状态转移方程:f[i][j] = min{f[i-1][j]+b[i], f[i-1][j-a[i]]}
状态转移循环,i 从1 到n,f 优化为一维数组,j 从N-1 逆序递推至0,
因为total_a>0 且价格都是整数。因为问题定义为“恰好”,f[0]初值为0,其
他初值为inf。(这块不清楚的请复习01 背包基础知识)。
状态转移过程中的购买总额计算公式如下图所示,图中红色字体的
total_a’和total_b’是转移过程中两个店的购买总额计算公式。
而实际情况中,total_a 显然不一定能够恰好达到N,而是大于或等于N,
所以代码第47 行,每次状态转移之前,若找到一个f[j]!=Inf 可行解,都要试探
性的先判断一下total_a 若转移到下一状态的话是否可能达到N,因为此时还未
进行状态转移,所以f[j]代表的是f[i-1][j],就是物品集E(i-1),经过调整后,
分到b 店的购买额。
此刻,若第i 件物品若选择在a 店购买,则:
a 店的购买总额=原先的total_a + j + a[i]
b 店的购买总额=f[j] + 原先的total_b-total_b_prefix[i]
若a 店的购买总额达到N,则购买总额=a 店的购买总额*0.95+b 店购
买总额,将其与最优解初值ans 比较,取更小者更新ans,否则ans 不变,注
意ans 的初值就是。这样状态转移完毕之后,得到的ans 就是最终的最优解。

三、烽火传递

分析:
要用动态规划的方法解决。我们可以写出这样的方程:
f[i]:=min{f[j]}+a[i] (i-m<=j<=i-1)
我们想到了用单调队列进行优化,由于随着i的循环,每次只有一个i进入决策区间也只有一个i出决策区间,由于每次选取决策区间中的最小值,所以维护一个单调递增序列,每次取出队首元素即可。
为什么可以将队尾元素无情的删去呢?由于后进队的序列同时满足在原序列中的位置更靠后和其在动态规划中的价值更大。这样选取这个元素就要比选取之前的任何一个决策要优,所以之前被删掉的决策都是无用的。
这道题的本质就是用单调队列维护了决策本身的价值和其在原序列中位置的同时单调。
要特别注意单调队列中的值是决策在原决策序列中的位置。

补充:
设f[i]表示点燃当前位置烽火台,且前i个满足要求的最小代价。
显然就有f[i]=min(f[j])+a[i](i-m<=j<=i-1)。
当然,这会超时,所以要有优化。
优化一:肯定是从前m个里选小的,涉及到区间最小值,可用线段树,时间复杂度将为O(nlogm)。
优化二:同样因为要选前m个最小的,使用单调队列,队列里存有不超过m个长度单位的值,每次取队首,进队时维护队列使其单调不下降,复杂度将为O(n)。

四、最长回文子序列

函数lps(seq,i,j)的用途是求字符串seq中区间[i,j]上的最长回文子序列长度(即原问题的子问题)。
如果seq[i]==seq[j],则取区间[i+1,j-1]的最长回文子序列,再在两头分别加上seq[i]和seq[j]仍是回文的,因此长度等于lps(seq,i+1,j-1)+2。
否则,取两个区间[i,j-1]和[i+1,j]中最长回文子序列长度的较大值(因为此时seq[i]和seq[j]不能同时选)。
答案即为lps(seq,0,n-1)。

搜索

Task 1:

深搜一下每一步怎么走。

Task 2:

深搜一下每一位放哪个数。

Task 3:

注意到如果确定了整个第一行、整个第一列的数字,那么我们实际上可以一行行地推出整个棋盘。
那么我们只要搜索第一行、第一列一共至多20个数字,然后判断是否符合条件就行了。
搜索的时候可以按字典序搜,找到一个解就可以直接退出程序了。

Task 4:

对于泥潭,我们看做两个格子u、v,走进去时会到达u,u到v需要1s,然后再从v走出去。
于是直接从起点开始广搜。

感谢阅读这么没有排版的文章

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mike_code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值