﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-Blackeagle's Coder Career</title><link>http://www.cppblog.com/blackeagle/</link><description>Welcome</description><language>zh-cn</language><lastBuildDate>Sun, 05 Apr 2026 13:56:38 GMT</lastBuildDate><pubDate>Sun, 05 Apr 2026 13:56:38 GMT</pubDate><ttl>60</ttl><item><title>转牛人博客 背包问题[1/3]</title><link>http://www.cppblog.com/blackeagle/archive/2008/07/31/57692.html</link><dc:creator>blackeagle</dc:creator><author>blackeagle</author><pubDate>Thu, 31 Jul 2008 15:04:00 GMT</pubDate><guid>http://www.cppblog.com/blackeagle/archive/2008/07/31/57692.html</guid><wfw:comment>http://www.cppblog.com/blackeagle/comments/57692.html</wfw:comment><comments>http://www.cppblog.com/blackeagle/archive/2008/07/31/57692.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/blackeagle/comments/commentRss/57692.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/blackeagle/services/trackbacks/57692.html</trackback:ping><description><![CDATA[<p><a href="http://www.concretevitamin.com.cn/informatics/Pack/Index.html">http://www.concretevitamin.com.cn/informatics/Pack/Index.html</a><br>背包问题九讲<br>version 1.1 build 20071115</p>
<p>前言 <br>目录 <br>第一讲 01背包问题 <br>第二讲 完全背包问题 <br>第三讲 多重背包问题 <br>第四讲 混合三种背包问题 <br>第五讲 二维费用的背包问题 <br>第六讲 分组的背包问题 <br>第七讲 有依赖的背包问题 <br>第八讲 泛化物品 <br>第九讲 背包问题问法的变化 <br>附录一：USACO中的背包问题 <br>附录二：背包问题的搜索解法 <br>联系方式 <br>致谢 <br>前言<br>本篇文章是我(dd_engi)正在进行中的一个雄心勃勃的写作计划的一部分，这个计划的内容是写作一份较为完善的NOIP难度的动态规划总结，名为《解动态规划题的基本思考方式》。现在你看到的是这个写作计划最先发布的一部分。</p>
<p>背包问题是一个经典的动态规划模型。它既简单形象容易理解，又在某种程度上能够揭示动态规划的本质，故不少教材都把它作为动态规划部分的第一道例题，我也将它放在我的写作计划的第一部分。</p>
<p>读本文最重要的是思考。因为我的语言和写作方式向来不以易于理解为长，思路也偶有跳跃的地方，后面更有需要大量思考才能理解的比较抽象的内容。更重要的是：不大量思考，绝对不可能学好动态规划这一信息学奥赛中最精致的部分。</p>
<p>你现在看到的是本文的v1.1版，发布于2007年11月15日。我会长期维护这份文本，把大家的意见和建议融入其中，也会不断加入我在OI学习以及将来可能的ACM-ICPC的征程中得到的新的心得。但目前本文还没有一个固定的发布页面，想了解本文是否有更新版本发布，可以在OIBH论坛中以&#8220;背包问题九讲&#8221;为关键字搜索贴子，每次比较重大的版本更新都会在这个论坛里发贴公布。也可以用&#8220;背包问题九讲&#8221;为关键字在搜索引擎中搜索以得到最新版本。</p>
<p>目录<br><strong>第一讲 01背包问题</strong><br>这是最基本的背包问题，每个物品最多只能放一次。</p>
<p>P01: 01背包问题<br>题目<br>有N件物品和一个容量为V的背包。第i件物品的费用是c[i]，价值是w[i]。求解将哪些物品装入背包可使价值总和最大。</p>
<p>基本思路<br>这是最基础的背包问题，特点是：每种物品仅有一件，可以选择放或不放。</p>
<p>用子问题定义状态：即f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。则其状态转移方程便是：</p>
<p>f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}</p>
<p>这个方程非常重要，基本上所有跟背包相关的问题的方程都是由它衍生出来的。所以有必要将它详细解释一下：&#8220;将前i件物品放入容量为v的背包中&#8221;这个子问题，若只考虑第i件物品的策略（放或不放），那么就可以转化为一个只牵扯前i-1件物品的问题。如果不放第i件物品，那么问题就转化为&#8220;前i-1件物品放入容量为v的背包中&#8221;，价值为f[i-1][v]；如果放第i件物品，那么问题就转化为&#8220;前i-1件物品放入剩下的容量为v-c[i]的背包中&#8221;，此时能获得的最大价值就是f[i-1][v-c[i]]再加上通过放入第i件物品获得的价值w[i]。</p>
<p>优化空间复杂度<br>以上方法的时间和空间复杂度均为O(VN)，其中时间复杂度应该已经不能再优化了，但空间复杂度却可以优化到O。</p>
<p>先考虑上面讲的基本思路如何实现，肯定是有一个主循环i=1..N，每次算出来二维数组f[i][0..V]的所有值。那么，如果只用一个数组f[0..V]，能不能保证第i次循环结束后f[v]中表示的就是我们定义的状态f[i][v]呢？f[i][v]是由f[i-1][v]和f[i-1][v-c[i]]两个子问题递推而来，能否保证在推f[i][v]时（也即在第i次主循环中推f[v]时）能够得到f[i-1][v]和f[i-1][v-c[i]]的值呢？事实上，这要求在每次主循环中我们以v=V..0的顺序推f[v]，这样才能保证推f[v]时f[v-c[i]]保存的是状态f[i-1][v-c[i]]的值。伪代码如下：</p>
<p>for i=1..N<br>&nbsp;&nbsp;&nbsp; for v=V..0<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f[v]=max{f[v],f[v-c[i]]+w[i]};<br>其中的f[v]=max{f[v],f[v-c[i]]}一句恰就相当于我们的转移方程f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]}，因为现在的f[v-c[i]]就相当于原来的f[i-1][v-c[i]]。如果将v的循环顺序从上面的逆序改成顺序的话，那么则成了f[i][v]由f[i][v-c[i]]推知，与本题意不符，但它却是另一个重要的背包问题P02最简捷的解决方案，故学习只用一维数组解01背包问题是十分必要的。</p>
<p>事实上，使用一维数组解01背包的程序在后面会被多次用到，所以这里抽象出一个处理一件01背包中的物品过程，以后的代码中直接调用不加说明。</p>
<p>过程ZeroOnePack，表示处理一件01背包中的物品，两个参数cost、weight分别表明这件物品的费用和价值。</p>
<p>procedure ZeroOnePack(cost,weight)<br>&nbsp;&nbsp;&nbsp; for v=V..cost<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f[v]=max{f[v],f[v-cost]+weight}<br>注意这个过程里的处理与前面给出的伪代码有所不同。前面的示例程序写成v=V..0是为了在程序中体现每个状态都按照方程求解了，避免不必要的思维复杂度。而这里既然已经抽象成看作黑箱的过程了，就可以加入优化。费用为cost的物品不会影响状态f[0..cost-1]，这是显然的。</p>
<p>有了这个过程以后，01背包问题的伪代码就可以这样写：</p>
<p>for i=1..N<br>&nbsp;&nbsp;&nbsp; ZeroOnePack(c[i],w[i]);<br>初始化的细节问题<br>我们看到的求最优解的背包问题题目中，事实上有两种不太相同的问法。有的题目要求&#8220;恰好装满背包&#8221;时的最优解，有的题目则并没有要求必须把背包装满。一种区别这两种问法的实现方法是在初始化的时候有所不同。</p>
<p>如果是第一种问法，要求恰好装满背包，那么在初始化时除了f[0]为0其它f[1..V]均设为-&#8734;，这样就可以保证最终得到的f[N]是一种恰好装满背包的最优解。</p>
<p>如果并没有要求必须把背包装满，而是只希望价格尽量大，初始化时应该将f[0..V]全部设为0。</p>
<p>为什么呢？可以这样理解：初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满，那么此时只有容量为0的背包可能被价值为0的nothing&#8220;恰好装满&#8221;，其它容量的背包均没有合法的解，属于未定义的状态，它们的值就都应该是-&#8734;了。如果背包并非必须被装满，那么任何容量的背包都有一个合法解&#8220;什么都不装&#8221;，这个解的价值为0，所以初始时状态的值也就全部为0了。</p>
<p>这个小技巧完全可以推广到其它类型的背包问题，后面也就不再对进行状态转移之前的初始化进行讲解。</p>
<p>一个常数优化<br>前面的伪代码中有 for v=V..1，可以将这个循环的下限进行改进。</p>
<p>由于只需要最后f[v]的值，倒推前一个物品，其实只要知道f[v-w[n]]即可。以此类推，对以第j个背包，其实只需要知道到f[v-sum{w[j..n]}]即可，即代码中的</p>
<p>for i=1..N<br>&nbsp;&nbsp;&nbsp; for v=V..0<br>可以改成</p>
<p>for i=1..n<br>&nbsp;&nbsp;&nbsp; bound=max{V-sum{w[i..n]},c[i]}<br>&nbsp;&nbsp;&nbsp; for v=V..bound<br>这对于V比较大时是有用的。</p>
<p>小结<br>01背包问题是最基本的背包问题，它包含了背包问题中设计状态、方程的最基本思想，另外，别的类型的背包问题往往也可以转换成01背包问题求解。故一定要仔细体会上面基本思路的得出方法，状态转移方程的意义，以及最后怎样优化的空间复杂度。<br>&nbsp;</p>
<p><strong>第二讲 完全背包问题</strong><br>第二个基本的背包问题模型，每种物品可以放无限多次。<br>P02: 完全背包问题<br>题目<br>有N种物品和一个容量为V的背包，每种物品都有无限件可用。第i种物品的费用是c[i]，价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量，且价值总和最大。</p>
<p>基本思路<br>这个问题非常类似于01背包问题，所不同的是每种物品有无限件。也就是从每种物品的角度考虑，与它相关的策略已并非取或不取两种，而是有取0件、取1件、取2件&#8230;&#8230;等很多种。如果仍然按照解01背包时的思路，令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程，像这样：</p>
<p>f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0&lt;=k*c[i]&lt;=v}</p>
<p>这跟01背包问题一样有O(VN)个状态需要求解，但求解每个状态的时间已经不是常数了，求解状态f[i][v]的时间是O(v/c[i])，总的复杂度可以认为是O(V*&#931;(V/c[i]))，是比较大的。</p>
<p>将01背包问题的基本思路加以改进，得到了这样一个清晰的方法。这说明01背包问题的方程的确是很重要，可以推及其它类型的背包问题。但我们还是试图改进这个复杂度。</p>
<p>一个简单有效的优化<br>完全背包问题有一个很简单有效的优化，是这样的：若两件物品i、j满足c[i]&lt;=c[j]且w[i]&gt;=w[j]，则将物品j去掉，不用考虑。这个优化的正确性显然：任何情况下都可将价值小费用高得j换成物美价廉的i，得到至少不会更差的方案。对于随机生成的数据，这个方法往往会大大减少物品的件数，从而加快速度。然而这个并不能改善最坏情况的复杂度，因为有可能特别设计的数据可以一件物品也去不掉。</p>
<p>这个优化可以简单的O(N^2)地实现，一般都可以承受。另外，针对背包问题而言，比较不错的一种方法是：首先将费用大于V的物品去掉，然后使用类似计数排序的做法，计算出费用相同的物品中价值最高的是哪个，可以O(V+N)地完成这个优化。这个不太重要的过程就不给出伪代码了，希望你能独立思考写出伪代码或程序。</p>
<p>转化为01背包问题求解<br>既然01背包问题是最基本的背包问题，那么我们可以考虑把完全背包问题转化为01背包问题来解。最简单的想法是，考虑到第i种物品最多选V/c[i]件，于是可以把第i种物品转化为V/c[i]件费用及价值均不变的物品，然后求解这个01背包问题。这样完全没有改进基本思路的时间复杂度，但这毕竟给了我们将完全背包问题转化为01背包问题的思路：将一种物品拆成多件物品。</p>
<p>更高效的转化方法是：把第i种物品拆成费用为c[i]*2^k、价值为w[i]*2^k的若干件物品，其中k满足c[i]*2^k&lt;=V。这是二进制的思想，因为不管最优策略选几件第i种物品，总可以表示成若干个2^k件物品的和。这样把每种物品拆成O(log V/c[i])件物品，是一个很大的改进。</p>
<p>但我们有更优的O(VN)的算法。</p>
<p>O(VN)的算法<br>这个算法使用一维数组，先看伪代码：</p>
<p>for i=1..N<br>&nbsp;&nbsp;&nbsp; for v=0..V<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f[v]=max{f[v],f[v-cost]+weight}<br>你会发现，这个伪代码与P01的伪代码只有v的循环次序不同而已。为什么这样一改就可行呢？首先想想为什么P01中要按照v=V..0的逆序来循环。这是因为要保证第i次循环中的状态f[i][v]是由状态f[i-1][v-c[i]]递推而来。换句话说，这正是为了保证每件物品只选一次，保证在考虑&#8220;选入第i件物品&#8221;这件策略时，依据的是一个绝无已经选入第i件物品的子结果f[i-1][v-c[i]]。而现在完全背包的特点恰是每种物品可选无限件，所以在考虑&#8220;加选一件第i种物品&#8221;这种策略时，却正需要一个可能已选入第i种物品的子结果f[i][v-c[i]]，所以就可以并且必须采用v=0..V的顺序循环。这就是这个简单的程序为何成立的道理。</p>
<p>值得一提的是，上面的伪代码中两层for循环的次序可以颠倒。这个结论有可能会带来算法时间常数上的优化。</p>
<p>这个算法也可以以另外的思路得出。例如，将基本思路中求解f[i][v-c[i]]的状态转移方程显式地写出来，代入原方程中，会发现该方程可以等价地变形成这种形式：</p>
<p>f[i][v]=max{f[i-1][v],f[i][v-c[i]]+w[i]}</p>
<p>将这个方程用一维数组实现，便得到了上面的伪代码。</p>
<p>最后抽象出处理一件完全背包类物品的过程伪代码：</p>
<p>procedure CompletePack(cost,weight)<br>&nbsp;&nbsp;&nbsp; for v=cost..V<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f[v]=max{f[v],f[v-c[i]]+w[i]}<br>总结<br>完全背包问题也是一个相当基础的背包问题，它有两个状态转移方程，分别在&#8220;基本思路&#8221;以及&#8220;O(VN)的算法&#8220;的小节中给出。希望你能够对这两个状态转移方程都仔细地体会，不仅记住，也要弄明白它们是怎么得出来的，最好能够自己想一种得到这些方程的方法。事实上，对每一道动态规划题目都思考其方程的意义以及如何得来，是加深对动态规划的理解、提高动态规划功力的好方法。</p>
<p><strong>第三讲 多重背包问题</strong><br>每种物品有一个固定的次数上限。<br>P03: 多重背包问题<br>题目<br>有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用，每件费用是c[i]，价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量，且价值总和最大。</p>
<p>基本算法<br>这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可，因为对于第i种物品有n[i]+1种策略：取0件，取1件&#8230;&#8230;取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值，则有状态转移方程：</p>
<p>f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0&lt;=k&lt;=n[i]}</p>
<p>复杂度是O(V*&#931;n[i])。</p>
<p>转化为01背包问题<br>另一种好想好写的基本方法是转化为01背包求解：把第i种物品换成n[i]件01背包中的物品，则得到了物品数为&#931;n[i]的01背包问题，直接求解，复杂度仍然是O(V*&#931;n[i])。</p>
<p>但是我们期望将它转化为01背包问题之后能够像完全背包一样降低复杂度。仍然考虑二进制的思想，我们考虑把第i种物品换成若干件物品，使得原问题中第i种物品可取的每种策略——取0..n[i]件——均能等价于取若干件代换以后的物品。另外，取超过n[i]件的策略必不能出现。</p>
<p>方法是：将第i种物品分成若干件物品，其中每件物品有一个系数，这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为1,2,4,...,2^(k-1),n[i]-2^k+1，且k是满足n[i]-2^k+1&gt;0的最大整数。例如，如果n[i]为13，就将这种物品分成系数分别为1,2,4,6的四件物品。</p>
<p>分成的这几件物品的系数和为n[i]，表明不可能取多于n[i]件的第i种物品。另外这种方法也能保证对于0..n[i]间的每一个整数，均可以用若干个系数的和表示，这个证明可以分0..2^k-1和2^k..n[i]两段来分别讨论得出，并不难，希望你自己思考尝试一下。</p>
<p>这样就将第i种物品分成了O(log n[i])种物品，将原问题转化为了复杂度为&lt;math&gt;O(V*&#931;log n[i])的01背包问题，是很大的改进。</p>
<p>下面给出O(log amount)时间处理一件多重背包中物品的过程，其中amount表示物品的数量：</p>
<p>procedure MultiplePack(cost,weight,amount)<br>&nbsp;&nbsp;&nbsp; if cost*amount&gt;=V<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CompletePack(cost,weight)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br>&nbsp;&nbsp;&nbsp; integer k=1<br>&nbsp;&nbsp;&nbsp; while k&lt;amount<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ZeroOnePack(k*cost,k*weight)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; amount=amount-k<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; k=k*2<br>&nbsp;&nbsp;&nbsp; ZeroOnePack(amount*cost,amount*weight)<br>希望你仔细体会这个伪代码，如果不太理解的话，不妨翻译成程序代码以后，单步执行几次，或者头脑加纸笔模拟一下，也许就会慢慢理解了。</p>
<p>O(VN)的算法<br>多重背包问题同样有O(VN)的算法。这个算法基于基本算法的状态转移方程，但应用单调队列的方法使每个状态的值可以以均摊O(1)的时间求解。由于用单调队列优化的DP已超出了NOIP的范围，故本文不再展开讲解。我最初了解到这个方法是在楼天成的&#8220;男人八题&#8221;幻灯片上。</p>
<p>小结<br>这里我们看到了将一个算法的复杂度由O(V*&#931;n[i])改进到O(V*&#931;log n[i])的过程，还知道了存在应用超出NOIP范围的知识的O(VN)算法。希望你特别注意&#8220;拆分物品&#8221;的思想和方法，自己证明一下它的正确性，并将完整的程序代码写出来。</p>
<p>&nbsp;</p>
<img src ="http://www.cppblog.com/blackeagle/aggbug/57692.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/blackeagle/" target="_blank">blackeagle</a> 2008-07-31 23:04 <a href="http://www.cppblog.com/blackeagle/archive/2008/07/31/57692.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转百度知道 动态规划类USACO题例</title><link>http://www.cppblog.com/blackeagle/archive/2008/07/31/57686.html</link><dc:creator>blackeagle</dc:creator><author>blackeagle</author><pubDate>Thu, 31 Jul 2008 14:38:00 GMT</pubDate><guid>http://www.cppblog.com/blackeagle/archive/2008/07/31/57686.html</guid><wfw:comment>http://www.cppblog.com/blackeagle/comments/57686.html</wfw:comment><comments>http://www.cppblog.com/blackeagle/archive/2008/07/31/57686.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/blackeagle/comments/commentRss/57686.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/blackeagle/services/trackbacks/57686.html</trackback:ping><description><![CDATA[USACO<br>2.2 Subset Sums<br>题目如下：<br>对于从1到N的连续整集合合，能划分成两个子集合，且保证每个集合的数字和是相等的。<br>举个例子，如果N=3，对于{1，2，3}能划分成两个子集合，他们每个的所有数字和是相等的：<br>and {1,2} <br>这是唯一一种分发（交换集合位置被认为是同一种划分方案，因此不会增加划分方案总数）<br>如果N=7，有四种方法能划分集合{1，2，3，4，5，6，7}，每一种分发的子集合各数字和是相等的: <br>{1,6,7} and {2,3,4,5} {注 1+6+7=2+3+4+5} <br>{2,5,7} and {1,3,4,6} <br>{3,4,7} and {1,2,5,6} <br>{1,2,4,7} and {3,5,6} <br>给出N，你的程序应该输出划分方案总数，如果不存在这样的划分方案，则输出0。程序不能预存结果直接输出。<br>PROGRAM NAME: subset<br>INPUT FORMAT<br><br>输入文件只有一行，且只有一个整数N<br>SAMPLE INPUT (file subset.in)<br>7<br>OUTPUT FORMAT<br>输出划分方案总数，如果不存在则输出0。<br>SAMPLE OUTPUT (file subset.out)<br>4 <br>参考程序如下：<br><br>#include &lt;fstream&gt;<br>using namespace std;<br>const unsigned int MAX_SUM = 1024;<br>int n;<br>unsigned long long int dyn[MAX_SUM];<br>ifstream fin ("subset.in");<br>ofstream fout ("subset.out");<br>int main() {<br>fin &gt;&gt; n;<br>fin.close();<br>int s = n*(n+1);<br>if (s % 4) {<br>fout &lt;&lt; 0 &lt;&lt; endl;<br>fout.close ();<br>return ;<br>}<br>s /= 4;<br>int i, j;<br>dyn [0] = 1;<br>for (i = 1; i &lt;= n; i++)<br>for (j = s; j &gt;= i; j--) <br>dyn[j] += dyn[j-i];<br>fout &lt;&lt; (dyn[s]/2) &lt;&lt; endl;<br>fout.close();<br>return 0;<br>}<br><br>USACO 2.3<br>Longest Prefix<br>题目如下：<br>在生物学中，一些生物的结构是用包含其要素的大写字母序列来表示的。生物学家对于把长的序列分解成较短的（称之为元素的）序列很感兴趣。 <br>如果一个集合 P 中的元素可以通过串联（允许重复；串联，相当于 Pascal 中的 &#8220;+&#8221; 运算符）组成一个序列 S ，那么我们认为序列 S 可以分解为 P 中的元素。并不是所有的元素都必须出现。举个例子，序列 ABABACABAAB 可以分解为下面集合中的元素： <br>{A, AB, BA, CA, BBC}<br>序列 S 的前面 K 个字符称作 S 中长度为 K 的前缀。设计一个程序，输入一个元素集合以及一个大写字母序列，计算这个序列最长的前缀的长度。 <br>PROGRAM NAME: prefix<br>INPUT FORMAT<br>输入数据的开头包括 1..200 个元素（长度为 1..10 ）组成的集合，用连续的以空格分开的字符串表示。字母全部是大写，数据可能不止一行。元素集合结束的标志是一个只包含一个 &#8220;.&#8221; 的行。集合中的元素没有重复。接着是大写字母序列 S ，长度为 1..200,000 ，用一行或者多行的字符串来表示，每行不超过 76 个字符。换行符并不是序列 S 的一部分。<br>SAMPLE INPUT (file prefix.in) <br>A AB BA CA BBC<br>.<br>ABABACABAABC<br>OUTPUT FORMAT<br>只有一行，输出一个整数，表示 S 能够分解成 P 中元素的最长前缀的长度。<br>SAMPLE OUTPUT (file prefix.out)<br>11<br><br>示例程序如下：<br><br>#include &lt;stdio.h&gt;<br>/* maximum number of primitives */<br>#define MAXP 200<br>/* maximum length of a primitive */<br>#define MAXL 10<br>char prim[MAXP+1][MAXL+1]; /* primitives */<br>int nump; /* number of primitives */<br>int start[200001]; /* is this prefix of the sequence expressible? */<br>char data[200000]; /* the sequence */<br>int ndata; /* length of the sequence */<br>int main(int argc, char **argv)<br>{<br>FILE *fout, *fin;<br>int best;<br>int lv, lv2, lv3;<br>if ((fin = fopen("prim.in", "r")) == NULL)<br>{<br>perror ("fopen fin");<br>exit(1);<br>}<br>if ((fout = fopen("prim.out", "w")) == NULL)<br>{<br>perror ("fopen fout");<br>exit(1);<br>}<br>/* read in primitives */<br>while (1)<br>{<br>fscanf (fin, "%s", prim[nump]);<br>if (prim[nump][0] != '.') nump++;<br>else break;<br>}<br>/* read in string, one line at a time */<br>ndata = 0;<br>while (fscanf (fin, "%s", data+ndata) == 1)<br>ndata += strlen(data+ndata);<br>start[0] = 1;<br>best = 0;<br>for (lv = 0; lv &lt; ndata; lv++)<br>if (start[lv]) <br>{ /* for each expressible prefix */<br>best = lv; /* we found a longer expressible prefix! */<br>/* for each primitive, determine the the sequence starting at<br>this location matches it */<br>for (lv2 = 0; lv2 &lt; nump; lv2++)<br>{<br>for (lv3 = 0; lv + lv3 &lt; ndata &amp;&amp; prim[lv2][lv3] &amp;&amp;<br>prim[lv2][lv3] == data[lv+lv3]; lv3++)<br>;<br>if (!prim[lv2][lv3]) /* it matched! */<br>start[lv + lv3] = 1; /* so the expanded prefix is also expressive */<br>}<br>} <br>/* see if the entire sequence is expressible */<br>if (start[ndata]) best = ndata; <br>fprintf (fout, "%i\n", best);<br>return 0;<br>}<br><br>USACO 3.1<br>Score Inflation<br>题目如下：<br>我们试着设计我们的竞赛以便人们能尽可能的多得分,这需要你的帮助。<br>我们可以从几个种类中选取竞赛的题目,这里的一个"种类"是指一个竞赛题目的集合,解决集合中的题目需要相同多的时间并且能得到相同的分数。<br>你的任务是写一个程序来告诉USACO的职员,应该从每一个种类中选取多少题目,使得解决题目的总耗时在竞赛规定的时间里并且总分最大。<br>输入包括竞赛的时间,M(1 &lt;= M &lt;= 10,000)和N,"种类"的数目1 &lt;= N &lt;= 10,000。<br>后面的每一行将包括两个整数来描述一个"种类":<br>第一个整数说明解决这种题目能得的分数(1 &lt;= points &lt;= 10000),第二整数说明解决这种题目所需的时间(1 &lt;= minutes &lt;= 10000)。<br>你的程序应该确定我们应该从每个"种类"中选多少道题目使得能在竞赛的时间中得到最大的分数。<br>来自任意的"种类"的题目数目可能任何非负数(0或更多)。<br>计算可能得到的最大分数。<br>PROGRAM NAME: inflate<br>INPUT FORMAT<br>第 1 行: M, N--竞赛的时间和题目"种类"的数目。 <br>第 2-N+1 行: 两个整数:每个"种类"题目的分数和耗时。 <br>SAMPLE INPUT (file inflate.in) <br>300 4<br>100 60<br>250 120<br>120 100<br>35 20<br>OUTPUT FORMAT<br>单独的一行包括那个在给定的限制里可能得到的最大的分数。<br>SAMPLE OUTPUT (file inflate.out)<br>605<br>{从第2个"种类"中选两题，第4个"种类"中选三题}<br><br><br>示例程序如下：<br><br>#include &lt;fstream.h&gt;<br>ifstream fin("inflate.in");<br>ofstream fout("inflate.out");<br>const short maxm = 10010;<br>long best[maxm], m, n;<br>void<br>main()<br>{<br>short i, j, len, pts;<br>fin &gt;&gt; m &gt;&gt; n;<br>for (j = 0; j &lt;= m; j++)<br>best[j] = 0;<br>for (i = 0; i &lt; n; i++) {<br>fin &gt;&gt; pts &gt;&gt; len;<br>for (j = len; j &lt;= m; j++)<br>if (best[j-len] + pts &gt; best[j])<br>best[j] = best[j-len] + pts;<br>}<br>fout &lt;&lt; best[m] &lt;&lt; endl; // 由于数组元素不减，末元素最大<br>}<br><br>USACO 3.3<br>A Game<br>题目如下：<br>有如下一个双人游戏:N(2 &lt;= N &lt;= 100)个正整数的序列放在一个游戏平台上，两人轮流从序列的两端取数，取数后该数字被去掉并累加到本玩家的得分中，当数取尽时，游戏结束。以最终得分多者为胜。 <br>编一个执行最优策略的程序，最优策略就是使自己能得到在当前情况下最大的可能的总分的策略。你的程序要始终为第二位玩家执行最优策略。<br>PROGRAM NAME: game1<br>INPUT FORMAT<br>第一行: 正整数N, 表示序列中正整数的个数。 <br>第二行至末尾: 用空格分隔的N个正整数（大小为1-200）。 <br>SAMPLE INPUT (file game1.in) <br>6 <br>4 7 2 9<br>5 2<br>OUTPUT FORMAT<br>只有一行，用空格分隔的两个整数: 依次为玩家一和玩家二最终的得分。 <br>SAMPLE OUTPUT (file game1.out)<br>18 11<br><br>参考程序如下：<br>#include &lt;stdio.h&gt;<br>#define NMAX 101<br>int best[NMAX][2], t[NMAX];<br>int n;<br>void <br>readx () {<br>int i, aux;<br>freopen ("game1.in", "r", stdin);<br>scanf ("%d", &amp;n);<br>for (i = 1; i &lt;= n; i++) {<br>scanf ("%d", &amp;aux);<br>t<em> = t[i - 1] + aux;<br>}<br>fclose (stdin);<br>}<br>inline int <br>min (int x, int y) {<br>return x &gt; y ? y : x;<br>}<br>void <br>solve () {<br>int i, l;<br>for (l = 1; l &lt;= n; l++)<br>for (i = 1; i + l &lt;= n + 1; i++)<br>best[l%2] = t[i + l - 1] - t[i - 1] - min (best[i + 1][(l - 1) % 2],<br>best[(l - 1) % 2]);<br>}<br>void writex () {<br>freopen ("game1.out", "w", stdout);<br>printf ("%d %d\n", best[1][n % 2], t[n] - best[1][n % 2]);<br>fclose (stdout);<br>}<br>int <br>main () {<br>readx ();<br>solve ();<br>writex ();<br>return 0;<br>}<br><br></em>USACO 3.4<br>Raucous Rockers<br>题目如下：<br>你刚刚得到了流行的&#8220;破锣摇滚&#8221;乐队录制的尚未发表的N(1 &lt;= N &lt;= 20)首歌的版权。你打算从中精选一些歌曲，发行M(1 &lt;= M &lt;= 20)张CD。每一张CD最多可以容纳T(1 &lt;= T &lt;= 20)分钟的音乐，一首歌不能分装在两张CD中。 <br>不巧你是一位古典音乐迷，不懂如何判定这些歌的艺术价值。于是你决定根据以下标准进行选择： <br>歌曲必须按照创作的时间顺序在CD盘上出现。 <br>选中的歌曲数目尽可能地多。 <br>PROGRAM NAME: rockers<br>INPUT FORMAT<br>第一行： 三个整数：N, T, M. <br>第二行： N个整数，分别表示每首歌的长度，按创作时间顺序排列。 <br>SAMPLE INPUT (file rockers.in)<br>4 5 2<br>4 3 4 2<br>OUTPUT FORMAT<br>一个整数，表示可以装进M张CD盘的乐曲的最大数目。 <br>SAMPLE OUTPUT (file rockers.out)<br>3<br><br>参考程序如下：<br><em>#include &lt;stdio.h&gt;<br>#define MAX 25<br>int dp[MAX][MAX][MAX], length[MAX];<br>int <br>main ()<br>{<br>FILE *in = fopen ("rockers.in", "r");<br>FILE *out = fopen ("rockers.out", "w");<br>int a, b, c, d, best, numsongs, cdlength, numcds;<br>fscanf (in, "%d%d%d", &amp;numsongs, &amp;cdlength, &amp;numcds);<br>for (a = 1; a &lt;= numsongs; a++)<br>fscanf (in, "%d", &amp;length[a]);<br>best = 0;<br>for (a = 0; a &lt; numcds; a++)/*当前cd */<br>for (b = 0; b &lt;= cdlength; b++) /* 已过的时间*/<br>for (c = 0; c &lt;= numsongs; c++) { /* 上一曲*/<br>for (d = c + 1; d &lt;= numsongs; d++) { /* 下一曲*/<br>if (b + length[d] &lt;= cdlength) {<br>if (dp[a][c] + 1 &gt; dp[a][b + length[d]][d])<br>dp[a][b + length[d]][d] = dp[a][c] + 1;<br>}<br>else {<br>if (dp[a][c] + 1 &gt; dp[a + 1][length[d]][d])<br>dp[a + 1][length[d]][d] = dp[a][c] + 1;<br>}<br>}<br>if (dp[a][c] &gt; best)<br>best = dp[a][c];<br>}<br>fprintf (out, "%d\n", best);<br>return 0;<br>}<br><br></em><strong>USACO<br>4.3 Buy Low, Buy Lower<br>&#8220;逢低吸纳&#8221;是炒股的一条成功秘诀。如果你想成为一个成功的投资者，就要遵守这条秘诀: <br>"逢低吸纳,越低越买" <br>这句话的意思是：每次你购买股票时的股价一定要比你上次购买时的股价低.按照这个规则购买股票的次数越多越好，看看你最多能按这个规则买几次。<br>给定连续的N天中每天的股价。你可以在任何一天购买一次股票，但是购买时的股价一定要比你上次购买时的股价低。写一个程序，求出最多能买几次股票。 <br>以下面这个表为例, 某几天的股价是: <br>天数 1 2 3 4 5 6 7 8 9 10 11 12<br>股价 68 69 54 64 68 64 70 67 78 62 98 87<br>这个例子中, 聪明的投资者(按上面的定义)，如果每次买股票时的股价都比上一次买时低，那么他最多能买4次股票。一种买法如下(可能有其他的买法): <br>天数 2 5 6 10<br>股价 69 68 64 62<br><br>PROGRAM NAME: buylow<br>INPUT FORMAT<br>第1行: N (1 &lt;= N &lt;= 5000), 表示能买股票的天数。 <br>第2行以下: N个正整数 (可能分多行) ，第i个正整数表示第i天的股价. 这些正整数大小不会超过longint(pascal)/long(c++). <br>SAMPLE INPUT (file buylow.in)<br>12<br>68 69 54 64 68 64 70 67<br>78 62 98 87<br>OUTPUT FORMAT<br>只有一行，输出两个整数： <br>能够买进股票的天数 <br>长度达到这个值的股票购买方案数量 <br>在计算解的数量的时候，如果两个解所组成的字符串相同，那么这样的两个解被认为是相同的（只能算做一个解）。因此，两个不同的购买方案可能产生同一个字符串，这样只能计算一次。 <br>SAMPLE OUTPUT (file buylow.out)<br>4 2<br><br>参考程序如下：<br></strong><strong><em>#include &lt;stdio.h&gt;<br>#include &lt;assert.h&gt;<br>#include &lt;stdlib.h&gt;<br><br>typedef struct BIGNUM *bignum_t;<br>struct BIGNUM<br>{<br>int val;<br>bignum_t next;<br>};<br>int num[5000];<br>int len[5000]; <br>int nlen; <br>bignum_t cnt[5000]; <br><br>bignum_t get_big(void)<br>{ <br>static bignum_t block;<br>static int size = 0;<br>if (size == 0)<br>{<br>block = (bignum_t)malloc(sizeof(*block)*128);<br>size = 128;<br>}<br>size--;<br>return block++;<br>}<br>/*初始化高精度数*/<br>void init_big(bignum_t *num, int val)<br>{<br>*num = get_big(); <br>/* initialize */<br>(*num)-&gt;val = val;<br>(*num)-&gt;next = NULL;<br>}<br><br>void add(bignum_t a, bignum_t b)<br>{<br>int c; /* carry */<br><br>c = 0;<br>while (b || c)<br>{<br>a-&gt;val += c;<br>if (b) a-&gt;val += b-&gt;val; <br>/* if a-&gt;val is too large, we need to carry */<br>c = (a-&gt;val / 1000000);<br>a-&gt;val = (a-&gt;val % 1000000);<br>if (b) b = b-&gt;next;<br>if (!a-&gt;next &amp;&amp; (b || c))<br>{ /* allocate if we need to */<br>a-&gt;next = get_big();<br>a = a-&gt;next; <br>a-&gt;val = 0;<br>a-&gt;next = NULL;<br>} else a = a-&gt;next;<br>}<br>}<br><br>void out_num(FILE *f, bignum_t v)<br>{<br>if (v-&gt;next) <br>{<br>out_num(f, v-&gt;next); <br>fprintf (f, "%06i", v-&gt;val);<br>}<br>else <br>fprintf (f, "%i", v-&gt;val); <br>}<br>int main(int argc, char **argv)<br>{<br>FILE *fout, *fin;<br>int lv, lv2;<br>int c;<br>int max;<br>int l;<br>bignum_t ans;<br>if ((fin = fopen("buylow.in", "r")) == NULL)<br>{<br>perror ("fopen fin");<br>exit(1);<br>}<br>if ((fout = fopen("buylow.out", "w")) == NULL)<br>{<br>perror ("fopen fout");<br>exit(1);<br>}<br><br>fscanf (fin, "%d", &amp;nlen);<br>for (lv = 0; lv &lt; nlen; lv++)<br>fscanf (fin, "%d", &amp;num[lv]);<br>/* 用DP计算最大长度*/<br>for (lv = 0; lv &lt; nlen; lv++)<br>{<br>max = 1;<br>for (lv2 = lv-1; lv2 &gt;= 0; lv2--)<br>if (num[lv2] &gt; num[lv] &amp;&amp; len[lv2]+1 &gt; max) max = len[lv2]+1;<br>len[lv] = max; <br>}<br>for (lv = 0; lv &lt; nlen; lv++)<br>{ <br>if (len[lv] == 1) init_big(&amp;cnt[lv], 1);<br>else<br>{<br>init_big(&amp;cnt[lv], 0);<br>l = -1;<br>max = len[lv]-1; <br>for (lv2 = lv-1; lv2 &gt;= 0; lv2--)<br>if (len[lv2] == max &amp;&amp; num[lv2] &gt; num[lv] &amp;&amp; num[lv2] != l)<br>add(cnt[lv], cnt[lv2]);<br>l = num[lv2];<br>}<br>}<br>}<br>/* 找最长串*/<br>max = 0;<br>for (lv = 0; lv &lt; nlen; lv++)<br>if (len[lv] &gt; max) max = len[lv];<br>init_big(&amp;ans, 0);<br>l = -1;<br>for (lv = nlen-1; lv &gt;= 0; lv--)<br>if (len[lv] == max &amp;&amp; num[lv] != l) <br>{<br>add(ans, cnt[lv]);<br>l = num[lv];<br>}<br>/* output answer */<br>fprintf (fout, "%i ", max);<br>out_num(fout, ans);<br>fprintf (fout, "\n");<br>return 0;<br>}<br></em></strong>
<img src ="http://www.cppblog.com/blackeagle/aggbug/57686.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/blackeagle/" target="_blank">blackeagle</a> 2008-07-31 22:38 <a href="http://www.cppblog.com/blackeagle/archive/2008/07/31/57686.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>暑期ppca结束了~总结一下</title><link>http://www.cppblog.com/blackeagle/archive/2008/07/31/57653.html</link><dc:creator>blackeagle</dc:creator><author>blackeagle</author><pubDate>Thu, 31 Jul 2008 08:47:00 GMT</pubDate><guid>http://www.cppblog.com/blackeagle/archive/2008/07/31/57653.html</guid><wfw:comment>http://www.cppblog.com/blackeagle/comments/57653.html</wfw:comment><comments>http://www.cppblog.com/blackeagle/archive/2008/07/31/57653.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/blackeagle/comments/commentRss/57653.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/blackeagle/services/trackbacks/57653.html</trackback:ping><description><![CDATA[作了前三章的题目，总体感觉题目的智商并不高,但很绕,或许这就是美国人的风格.<br>主要的印象是:<br>[1]DP很牛X，解决很多复杂的问题，基本思想是把责任推给下一代。<br>[2]牛顿消元解方程组挺有用，应该熟练掌握。<br>[3]计算几何实用性很强，准确地说是如果不用的话狂拼解析几何，题目很难做出来。<br>[4]代码的规范和艺术应当提倡，使得剪枝、优化、Debug等操作十分方便，而且有条理的话容易发现代码的问题。<br>陆续贴出相关内容及知识点~~
<img src ="http://www.cppblog.com/blackeagle/aggbug/57653.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/blackeagle/" target="_blank">blackeagle</a> 2008-07-31 16:47 <a href="http://www.cppblog.com/blackeagle/archive/2008/07/31/57653.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>开始写新的blog</title><link>http://www.cppblog.com/blackeagle/archive/2008/07/16/56331.html</link><dc:creator>blackeagle</dc:creator><author>blackeagle</author><pubDate>Wed, 16 Jul 2008 11:08:00 GMT</pubDate><guid>http://www.cppblog.com/blackeagle/archive/2008/07/16/56331.html</guid><wfw:comment>http://www.cppblog.com/blackeagle/comments/56331.html</wfw:comment><comments>http://www.cppblog.com/blackeagle/archive/2008/07/16/56331.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.cppblog.com/blackeagle/comments/commentRss/56331.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/blackeagle/services/trackbacks/56331.html</trackback:ping><description><![CDATA[这貌似是我第6或是第7个blog了。<br>这个blog是专门记述我的coder生涯的，开始想在xiaonei上写，怕被鄙视，于是就在这里开了一个。<br>初学CS,C++什么的用的还不熟，大家多包涵。<br>我会不定期地更新，希望大家捧场咯。
<img src ="http://www.cppblog.com/blackeagle/aggbug/56331.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/blackeagle/" target="_blank">blackeagle</a> 2008-07-16 19:08 <a href="http://www.cppblog.com/blackeagle/archive/2008/07/16/56331.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>