贪心整理&一本通1431:钓鱼题解

本文深入解析了一道关于贪心算法的编程题目,通过实例详细解释了如何将动态规划问题转化为贪心策略,提供了AC代码并总结了贪心算法的确定策略、证明方法及注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目传送

 

其实有一个更正经的题解

看了许久,发现这题貌似就是一个动态规划啊,但毕竟是贪心题库里的题,还是想想用贪心解吧。

经过(借鉴大佬思路)十分复杂的思考后,终于理解出了这题的贪心思路。该题的难点主要在最后可在任意湖边停住,而且不能往回走,在一个湖钓鱼时的效率还会越来越少。常规的思路看来是不行的了,题目好多动态未知的量,唯有我们更换角度,“化动为静”:

即然最后不知道停在哪个湖,那就分类讨论呗。把停在每个湖的最优解全部求出,在最后取个最优解不就行了吗?发现当我们知道主人公最后停在哪个湖后,她的路径也就唯一确定了(例如佳佳最后停在了第i个湖,那么她的路径一定是1—》2—》3—》。。。—》i),同时她的纯钓鱼时间可由总空闲时间减去行程时间唯一确定。考虑从哪个湖钓鱼一个5分钟,就相当于在路径1—》2—》3—》。。。—》i中的一个节点上“堆”上一个标记表示在这个湖又钓了5分钟的鱼,显然这里可用贪心策略,每次标记目前为止五分钟钓鱼数目最大的那个湖,并使当前记录答案的sumi+=在那个湖又钓的鱼数。最后比较所有的sumi(i=1,2,...,n)取最大的输出就行了。

还不懂?也许看看AC代码就懂了:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cctype>
 4 #include<vector>
 5 using namespace std;
 6 
 7 int ans;
 8 
 9 vector<int>fish,lesss,t;//每个湖第一个 5 分钟能钓到鱼的数量,每个湖每钓鱼5分钟较前5分钟钓的鱼数减少的数量,如题意 
10 vector<int>get,tmpfish;//从第一个湖走到第i个湖所需时间,每个湖的当前5分钟能钓到的鱼数 
11 
12 char ch;
13  
14 inline int read()//快读(亦名读入优化) 
15 {
16     ans=0;
17     ch=getchar();
18     while(!isdigit(ch)) ch=getchar();
19     while(isdigit(ch)) ans=(ans<<3)+(ans<<1)+ch-'0',ch=getchar();
20     return ans;
21 }
22 
23 inline void init()//初始处理动态数组,因为希望动态数组的下标从1开始 
24 {
25     fish.push_back(0);
26     lesss.push_back(0);
27     t.push_back(0);
28     get.push_back(0);
29     get.push_back(0);//注意get数组在主函数是从下标为2的开始处理的,因此需要多填一个0。 
30     tmpfish.push_back(0);
31 }//为什么要填0?为了与普通全局数组的性质相同(定义时默认全初始化为0) 
32 
33 int main()
34 {
35     init();
36     int n=read(),h=read()*12;
37     for(int i=1;i<=n;i++) fish.push_back(read());
38     for(int i=1;i<=n;i++) lesss.push_back(read());
39     for(int i=1;i<n;i++) t.push_back(read());
40     for(int i=2;i<=n;i++) get.push_back(get[i-1]+t[i-1]);
41     int mava,mapo,tmphours,matot=0;//当前贪心找到的最大值,当前贪心找到的最大值对应的下标(即湖的编号),当前纯钓鱼时间,最后的答案。 
42     for(int i=1;i<=n;i++) tmpfish.push_back(0);
43     for(int k=1;k<=n;k++)
44         if(h>get[k])
45         {
46             tmphours=h-get[k];//可用的纯钓鱼时间 
47             for(int i=1;i<=k;i++) tmpfish[i]=fish[i];//初始化 
48             int sum=0;//记录的当佳佳最后停在第k个湖时的当前答案 
49             while(tmphours>0)
50             {
51                 mava=-2000000000;
52                 mapo=0;
53                 for(int i=1;i<=k;i++)//贪心选择 
54                     if(mava<tmpfish[i])
55                     {
56                         mava=tmpfish[i];
57                         mapo=i;
58                     }
59                 if(mava<=0) break;//没鱼可钓就直接退出 
60                 sum+=mava;
61                 if(tmpfish[mapo]>lesss[mapo]) tmpfish[mapo]-=lesss[mapo];
62                 else tmpfish[mapo]=0;
63                 tmphours--;
64             } 
65             if(sum>matot) matot=sum;
66         }
67         else break;
68     printf("%d",matot);    
69     return 0;
70 }

最后再总结一下贪心吧:

贪心策略的确定:看到题时,可根据生活经验(滑稽)确认一个直觉指引的贪心策略。对付简单题很有用。

        关注一下与题目有关的性质(可以是由数学推导的式子,或是题中描述的物品的一些跟生活有关的物理性质)基本跟贪心有关的题都会有找某个方面的最大值或最小值。

贪心策略的证明: 直接数学推导。

        假设有一个更优的方案,反证。

        玄学占卜

贪心的几点注意:当整体最优解可由局部最优解推出(并不只局限与一种策略)时才可用贪心。(否则用动态规划)

        基本能用贪心的动态规划都行,不过一般贪心的复杂度要优于动态规划。

转载于:https://www.cnblogs.com/InductiveSorting-QYF/p/10987715.html

&lt;think&gt;我们正在解决信息学奥赛一本中的题目:删除单词后缀(题目编号1141)。 题目要求:给定一个单词,如果单词以&quot;er&quot;、&quot;ly&quot;或&quot;ing&quot;结尾,则删除这个后缀(只删除一个后缀,且题目保证删除后单词长度不为0)。 注意:后缀的删除优先级是,如果有多个后缀同时满足,优先删除最长的后缀(即先检查3字符的后缀&quot;ing&quot;,再检查2字符的后缀&quot;er&quot;和&quot;ly&quot;)。 解题思路: 1. 读取输入的单词。 2. 获取单词长度。 3. 按照优先级检查后缀(先检查3字符的&quot;ing&quot;,再检查2字符的&quot;er&quot;和&quot;ly&quot;): - 如果单词长度大于等于3且最后三个字符是&quot;ing&quot;,则删除这三个字符(即新字符串长度为原长度减3)。 - 否则,如果单词长度大于等于2且最后两个字符是&quot;er&quot;,则删除这两个字符。 - 否则,如果单词长度大于等于2且最后两个字符是&quot;ly&quot;,则删除这两个字符。 4. 输出处理后的字符串。 注意:题目只要求删除一个后缀,所以一旦匹配到一个后缀并删除,就不再检查其他后缀。 代码实现(C++):&lt;/think&gt;# 信息学奥赛一本 1141:删除单词后缀 解题方法 ## 题目分析 题目要求删除单词的特定后缀(`er`、`ly` 或 `ing`),但只删除一个后缀。关键点在于: 1. **后缀优先级**:优先删除最长的后缀(`ing` &gt; `er`/`ly`) 2. **后缀长度**:`ing` 是3字符后缀,`er` 和 `ly` 是2字符后缀 3. **边界条件**:删除后单词长度不为零 ## 高效解题思路 ### 算法步骤 1. **获取单词长度**:确定字符串长度 `len` 2. **检查后缀优先级**: - 先检查3字符后缀 `ing`(最长后缀优先) - 再检查2字符后缀 `er` - 最后检查2字符后缀 `ly` 3. **调整长度**:匹配到后缀则减少对应长度 4. **输出结果**:截取原始字符串的前 `len` 个字符 ### 时间复杂度 $O(1)$,所有操作都是常数时间操作(后缀检查使用子串截取,最大检查长度固定为3) ## C++ 代码实现 ```cpp #include &lt;iostream&gt; #include &lt;string&gt; using namespace std; int main() { string s; cin &gt;&gt; s; // 读取输入单词 int len = s.size(); // 获取原始长度 // 优先检查并删除最长后缀 &quot;ing&quot;(3字符) if (len &gt;= 3 &amp;&amp; s.substr(len-3, 3) == &quot;ing&quot;) { len -= 3; // 长度减少3 } // 检查并删除 &quot;er&quot;(2字符) else if (len &gt;= 2 &amp;&amp; s.substr(len-2, 2) == &quot;er&quot;) { len -= 2; // 长度减少2 } // 检查并删除 &quot;ly&quot;(2字符) else if (len &gt;= 2 &amp;&amp; s.substr(len-2, 2) == &quot;ly&quot;) { len -= 2; // 长度减少2 } cout &lt;&lt; s.substr(0, len) &lt;&lt; endl; // 输出处理后的单词 return 0; } ``` ## 关键点解析 1. **后缀检查顺序**:必须按照 `ing` &rarr; `er` &rarr; `ly` 的顺序检查,确保优先删除最长后缀 2. **边界保护**:每次检查前验证字符串长度(`len &gt;= 2` 或 `len &gt;= 3`)避免越界 3. **子串截取**:使用 `substr(pos, len)` 方法: - `s.substr(len-3, 3)` 获取最后3个字符 - `s.substr(0, len)` 输出处理后的单词 4. **长度调整**:匹配到后缀时修改长度变量 `len`,最后统一截取 ## 测试用例验证 | 输入 | 输出 | 说明 | |---------------|------------|--------------------------| | `referer` | `refer` | 删除 `er` 后缀 | | `friendly` | `friend` | 删除 `ly` 后缀 | | `jumping` | `jump` | 删除 `ing` 后缀 | | `ingly` | `ly` | 只删除一个后缀(`ing`) | | `hello` | `hello` | 无匹配后缀 | | `sing` | `s` | 删除 `ing` 后缀 | ## 算法优化 对于性能要求更高的场景,可避免使用 `substr`,直接比较最后几个字符: ```cpp // 优化版本(避免substr开销) if (len &gt;= 3 &amp;&amp; s[len-3]==&#39;i&#39; &amp;&amp; s[len-2]==&#39;n&#39; &amp;&amp; s[len-1]==&#39;g&#39;) { len -= 3; } else if (len &gt;= 2 &amp;&amp; s[len-2]==&#39;e&#39; &amp;&amp; s[len-1]==&#39;r&#39;) { len -= 2; } else if (len &gt;= 2 &amp;&amp; s[len-2]==&#39;l&#39; &amp;&amp; s[len-1]==&#39;y&#39;) { len -= 2; } ``` ## 常见错误规避 1. **顺序错误**:若先检查 `er` 再检查 `inger`,会导致错误删除部分后缀 2. **长度检查缺失**:未检查 `len &gt;= 3` 直接取最后3字符会导致运行时错误 3. **多次删除**:题目要求只删除一个后缀,无需循环处理 &gt; 本题考察字符串基础操作和条件判断逻辑,是信息学奥赛中典型的字符串处理入门题[^1]。 [^1]:《信息学奥赛一本》字符串处理专题解析,详见 [ybt 题解精要](https://oi-wiki.org/string/)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值