﻿<?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++博客-JulyRina's blog-随笔分类-算法专题</title><link>http://www.cppblog.com/JulyRina/category/21058.html</link><description>welcome to July Rina's blog</description><language>zh-cn</language><lastBuildDate>Mon, 09 Mar 2015 18:34:01 GMT</lastBuildDate><pubDate>Mon, 09 Mar 2015 18:34:01 GMT</pubDate><ttl>60</ttl><item><title>稳定婚姻问题简介</title><link>http://www.cppblog.com/JulyRina/archive/2015/03/09/209981.html</link><dc:creator>JulyRina</dc:creator><author>JulyRina</author><pubDate>Mon, 09 Mar 2015 10:57:00 GMT</pubDate><guid>http://www.cppblog.com/JulyRina/archive/2015/03/09/209981.html</guid><wfw:comment>http://www.cppblog.com/JulyRina/comments/209981.html</wfw:comment><comments>http://www.cppblog.com/JulyRina/archive/2015/03/09/209981.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/JulyRina/comments/commentRss/209981.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/JulyRina/services/trackbacks/209981.html</trackback:ping><description><![CDATA[问题来自于一场&#8220;3分钟相亲&#8221;活动，参加活动的有n位男士和n位女士。要求每位男士都要和所有的女士进行短暂的单独交流，并为她们打分，然后按照喜欢程度，对每一位女士进行排序；同样的，每位女士也要对所有男士进行打分和排序。<br />
在这之后我们为选择策略为这n位男士和n位女士配对。使得在婚后不会有&#8220;出轨&#8221;的情况发生。<br />
这里的&#8220;出轨&#8221;是什么意思：<span style="font-size: 10pt; color: red;">图片来自网络，仅作举例之用</span><br />
<img src="http://www.cppblog.com/images/cppblog_com/julyrina/稳定婚姻问题.png" width="833" height="598" alt="" /><br />
如果以下两种情况之一发生，则会发生出轨：
<ol>
     <li>如果第一对夫妻中的妻子在婚后觉得自己的丈夫没有第二对夫妻中的丈夫帅；第二对夫妻中的丈夫同样也觉得自己的妻子没有第一对夫妻中的妻子漂亮</li>
     <li>如果第一对夫妻中的丈夫在婚后觉得自己的妻子没有第二对夫妻中的妻子美；第二对夫妻中的妻子同样也觉得自己的丈夫没有第一对夫妻中的丈夫帅</li>
</ol>
解决稳定婚姻的算法之一：<br />
<strong>延迟认可算法（Gale-Shapley算法）</strong><br />
先对所有男士进行落选标记，称其为自由男。当存在自由男时，进行以下操作：<br />
<ol>
     <li>每一位自由男在所有尚未拒绝她的女士中选择一位被他排名最优先的女士；</li>
     <li>每一位女士将正在追求她的自由男与其当前男友进行比较，选择其中排名优先的男士作为其男友，即若自由男优于当前男友，则抛弃前男友；否则保留其男友，拒绝自由男。</li>
     <li>若某男士被其女友抛弃，重新变成自由男。</li>
</ol>
在算法执行期间，自由男们<strong>主动出击</strong>，依次对最喜欢和次喜欢的女人求爱，一旦被接受，即失去自由身，进入订婚状态；而女人们则采取<strong>&#8220;守株待兔&#8221;</strong>和<strong>&#8220;喜新厌旧&#8221;</strong>策略，对前来求爱的男士进行选择：若该男子比未婚夫强，则悔婚，选择新的未婚夫；否则拒绝该男子的求婚。被女友抛弃的男人重获自由身，重新拥有了追求女人的权利&#8212;&#8212;当然，新的追求对象比不过前女友。<br />
这样，在算法执行期间，每个人都有可能订婚多次&#8212;&#8212;也有可能一开始就找到了自己的最爱，从一而终&#8212;&#8212;每订一次婚，女人们的选择就会更有利，而男人们的品味则越来越差。只要男女生的数量相等，则经过多轮求婚，订婚，悔婚和再订婚之后，每位男女最终都会找到合适的伴侣&#8212;&#8212;虽然不一定是自己的最爱（男人没能追到自己的最爱，或女人没有等到自己的最爱来追求），但绝对不会出现&#8220;虽然彼此相爱，却不能在一起&#8221;的悲剧，所有人都会组成稳定的婚姻。<img src ="http://www.cppblog.com/JulyRina/aggbug/209981.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/JulyRina/" target="_blank">JulyRina</a> 2015-03-09 18:57 <a href="http://www.cppblog.com/JulyRina/archive/2015/03/09/209981.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>字典树简介</title><link>http://www.cppblog.com/JulyRina/archive/2015/03/09/209980.html</link><dc:creator>JulyRina</dc:creator><author>JulyRina</author><pubDate>Mon, 09 Mar 2015 10:55:00 GMT</pubDate><guid>http://www.cppblog.com/JulyRina/archive/2015/03/09/209980.html</guid><wfw:comment>http://www.cppblog.com/JulyRina/comments/209980.html</wfw:comment><comments>http://www.cppblog.com/JulyRina/archive/2015/03/09/209980.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/JulyRina/comments/commentRss/209980.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/JulyRina/services/trackbacks/209980.html</trackback:ping><description><![CDATA[字典树是一种树形数据结构，他有如下特点：<br />
&nbsp;&nbsp;&nbsp;&nbsp;每个节点都有固定个数的指向儿子节点的指针，她的儿子某一个节点（如果存在的话）包含的信息就是该节点的下一个字符。<br />
&nbsp;&nbsp;&nbsp;&nbsp;根节点不包含字符，除根节点外每一个节点都只包含一个字符； 从根节点到某一节点，路径上经过的字符连接起来，为该节点对应的字符串； 每个节点的所有子节点包含的字符都不相同。<br />
<strong>例：</strong>作为一个简单的演示，这里我们稍微忽略一些细节。下面的这棵树就是一个简单的<u>字典树</u>的例子：<br /><img src="http://www.cppblog.com/images/cppblog_com/julyrina/字典树01.png" width="696" height="592" alt="" /><br />
<pre><font size="4px">如图所示，如果我们按行存储这些数据：
<u>apple</u>
<u>append</u>
<u>and</u>
<u>antiy</u>
<u>banana</u>
<u>band</u>
我们需要5+6+3+5+6+4=29 B 的空间。
但是字典树只需要20 B 的空间。
这在数据量更大的时候能起到更好的效果。</font></pre>
<br />
字典树能够线性时间范围内实现数据的增删改查。<img src ="http://www.cppblog.com/JulyRina/aggbug/209980.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/JulyRina/" target="_blank">JulyRina</a> 2015-03-09 18:55 <a href="http://www.cppblog.com/JulyRina/archive/2015/03/09/209980.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【算法导论（第三版）】第二章学习笔记</title><link>http://www.cppblog.com/JulyRina/archive/2015/03/07/209956.html</link><dc:creator>JulyRina</dc:creator><author>JulyRina</author><pubDate>Sat, 07 Mar 2015 07:19:00 GMT</pubDate><guid>http://www.cppblog.com/JulyRina/archive/2015/03/07/209956.html</guid><wfw:comment>http://www.cppblog.com/JulyRina/comments/209956.html</wfw:comment><comments>http://www.cppblog.com/JulyRina/archive/2015/03/07/209956.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/JulyRina/comments/commentRss/209956.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/JulyRina/services/trackbacks/209956.html</trackback:ping><description><![CDATA[【练习】<br />
2.3-2 MERGE的改进<br />
<div style="font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
--><span style="color: #0000FF; ">void</span>&nbsp;MERGE(<span style="color: #0000FF; ">int</span>&nbsp;*A,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;p,<span style="color: #0000FF; ">int</span>&nbsp;q,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;r)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;B[maxn]&nbsp;,&nbsp;i&nbsp;=&nbsp;p&nbsp;,&nbsp;j&nbsp;=&nbsp;q+1&nbsp;,&nbsp;k&nbsp;=&nbsp;0;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">while</span>(k&nbsp;&lt;&nbsp;r&nbsp;-&nbsp;p&nbsp;+&nbsp;1)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>(i&nbsp;&gt;&nbsp;q&nbsp;||&nbsp;j&nbsp;&lt;=&nbsp;r&nbsp;&amp;&amp;&nbsp;A[i]&nbsp;&gt;&nbsp;A[j])&nbsp;B[k++]&nbsp;=&nbsp;A[j++];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">else</span>&nbsp;B[k++]&nbsp;=&nbsp;A[i++];<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">for</span>(i=0;i&lt;r-p+1;i++)&nbsp;A[p+i]&nbsp;=&nbsp;B[i];<br />
}</div>
<br />2.3-5 二分查找的C++代码<br />
<div style="font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
--><span style="color: #0000FF; ">int</span>&nbsp;find(<span style="color: #0000FF; ">int</span>&nbsp;*a,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;l,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;r,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;value)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>(l&nbsp;==&nbsp;r)&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;l;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;mid&nbsp;=&nbsp;(l+r)&nbsp;&gt;&gt;&nbsp;1;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>(a[mid]&nbsp;&gt;=&nbsp;value)&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;find(a,&nbsp;l,&nbsp;mid,&nbsp;value);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">else</span>&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;find(a&nbsp;,&nbsp;mid+1,&nbsp;r&nbsp;,&nbsp;value);<br />
}</div>
<br />*2.3-7 （这道题其实有O(n)的算法，而且写起来更方便些）这里是O(nlogn)的算法<br />
O(nlogn)算法思想：1.首先进行排序；2.然后枚举每一个小于等于x/2的数S[i]，二分查找对应的x-S[i]是否存在<br />
<div style="font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->#include&nbsp;&lt;cstdio&gt;<br />
#include&nbsp;&lt;cstring&gt;<br />
#include&nbsp;&lt;iostream&gt;<br />
#include&nbsp;&lt;algorithm&gt;<br />
<span style="color: #0000FF; ">using</span>&nbsp;<span style="color: #0000FF; ">namespace</span>&nbsp;std;<br />
<span style="color: #0000FF; ">const</span>&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;maxn&nbsp;=&nbsp;1010;<br />
<span style="color: #0000FF; ">bool</span>&nbsp;findx(<span style="color: #0000FF; ">int</span>&nbsp;*S,<span style="color: #0000FF; ">int</span>&nbsp;n,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;x,<span style="color: #0000FF; ">int</span>&nbsp;l,<span style="color: #0000FF; ">int</span>&nbsp;r)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>(l&nbsp;&gt;&nbsp;r)&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;<span style="color: #0000FF; ">false</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>(l&nbsp;==r)&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;S[l]&nbsp;==&nbsp;x;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;mid&nbsp;=&nbsp;(l+r)&nbsp;&gt;&gt;&nbsp;1;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>(S[mid]&nbsp;&gt;=&nbsp;x)&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;findx(S,&nbsp;n,&nbsp;x,&nbsp;l,&nbsp;mid);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">else</span>&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;findx(S,&nbsp;n,&nbsp;x,&nbsp;mid+1,&nbsp;r);<br />
}<br />
<span style="color: #0000FF; ">bool</span>&nbsp;check(<span style="color: #0000FF; ">int</span>&nbsp;*S,<span style="color: #0000FF; ">int</span>&nbsp;n,<span style="color: #0000FF; ">int</span>&nbsp;x)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">for</span>(<span style="color: #0000FF; ">int</span>&nbsp;i=0;S[i]&lt;=x/2&nbsp;&amp;&amp;&nbsp;i&nbsp;&lt;&nbsp;n;i++)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>(findx(S,&nbsp;n,&nbsp;x-S[i],&nbsp;i+1,&nbsp;n-1))&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;<span style="color: #0000FF; ">true</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;<span style="color: #0000FF; ">false</span>;<br />
}<br />
<span style="color: #0000FF; ">int</span>&nbsp;main()&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;S[1010]&nbsp;,&nbsp;x&nbsp;,&nbsp;n;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">while</span>(~scanf("%d%d"&nbsp;,&nbsp;&amp;n&nbsp;,&nbsp;&amp;x))&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">for</span>(<span style="color: #0000FF; ">int</span>&nbsp;i=0;i&lt;n;i++)&nbsp;cin&nbsp;&gt;&gt;&nbsp;S[i];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>(check(S,&nbsp;n,&nbsp;x))&nbsp;puts("yes");<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">else</span>&nbsp;puts("no");<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;0;<br />
}</div>
<br />O(n)的方法是在数的范围不是特别大的时候（或者数的范围比较大，此时采用hash的方法）标记的方法，这里假设数的范围&lt;=10000，并且假设数没有重复的情况下，其他情况稍许改变一下就行：<br />
<div style="font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->#include&nbsp;&lt;cstdio&gt;<br />
#include&nbsp;&lt;cstring&gt;<br />
#include&nbsp;&lt;iostream&gt;<br />
#include&nbsp;&lt;algorithm&gt;<br />
<span style="color: #0000FF; ">using</span>&nbsp;<span style="color: #0000FF; ">namespace</span>&nbsp;std;<br />
<span style="color: #0000FF; ">const</span>&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;maxn&nbsp;=&nbsp;1010;<br />
<br />
<span style="color: #0000FF; ">bool</span>&nbsp;check(<span style="color: #0000FF; ">int</span>&nbsp;*S,<span style="color: #0000FF; ">int</span>&nbsp;n,<span style="color: #0000FF; ">int</span>&nbsp;x)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">bool</span>&nbsp;vis[10001]&nbsp;=&nbsp;{0};<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">for</span>(<span style="color: #0000FF; ">int</span>&nbsp;i=0;i&lt;n;i++)&nbsp;vis[x-S[i]]&nbsp;=&nbsp;<span style="color: #0000FF; ">true</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">for</span>(<span style="color: #0000FF; ">int</span>&nbsp;i=0;i&lt;n;i++)&nbsp;<span style="color: #0000FF; ">if</span>(vis[S[i]])&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;<span style="color: #0000FF; ">true</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;<span style="color: #0000FF; ">false</span>;<br />
}<br />
<span style="color: #0000FF; ">int</span>&nbsp;n&nbsp;,x&nbsp;,&nbsp;S[maxn];<br />
<span style="color: #0000FF; ">int</span>&nbsp;main()&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">while</span>(~scanf("%d%d"&nbsp;,&nbsp;&amp;n&nbsp;,&nbsp;&amp;x))&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">for</span>(<span style="color: #0000FF; ">int</span>&nbsp;i=0;i&lt;n;i++)&nbsp;cin&nbsp;&gt;&gt;&nbsp;S[i];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>(check(S,&nbsp;n,&nbsp;x))&nbsp;puts("yes");<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">else</span>&nbsp;puts("no");<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;0;<br />
}</div>
<br />2-4（逆序对）：这道题就是在归并排序中得到逆序对，具体见代码：<br />
<div style="font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->#include&nbsp;&lt;cstdio&gt;<br />
#include&nbsp;&lt;cstring&gt;<br />
#include&nbsp;&lt;iostream&gt;<br />
#include&nbsp;&lt;algorithm&gt;<br />
<span style="color: #0000FF; ">using</span>&nbsp;<span style="color: #0000FF; ">namespace</span>&nbsp;std;<br />
<span style="color: #0000FF; ">const</span>&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;maxn&nbsp;=&nbsp;1010;<br />
<span style="color: #0000FF; ">int</span>&nbsp;ans;<br />
<span style="color: #0000FF; ">void</span>&nbsp;merge_sort(<span style="color: #0000FF; ">int</span>&nbsp;*A,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;l,<span style="color: #0000FF; ">int</span>&nbsp;r)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>(l&nbsp;&gt;=&nbsp;r)&nbsp;<span style="color: #0000FF; ">return</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;mid&nbsp;=&nbsp;(l+r)&nbsp;&gt;&gt;&nbsp;1;<br />
&nbsp;&nbsp;&nbsp;&nbsp;merge_sort(A,&nbsp;l,&nbsp;mid);<br />
&nbsp;&nbsp;&nbsp;&nbsp;merge_sort(A,&nbsp;mid+1,&nbsp;r);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;i&nbsp;=&nbsp;l&nbsp;,&nbsp;j&nbsp;=&nbsp;mid+1&nbsp;,B[maxn]&nbsp;,&nbsp;k&nbsp;=&nbsp;l;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">while</span>(i&nbsp;&lt;=&nbsp;mid&nbsp;||&nbsp;j&nbsp;&lt;=&nbsp;r)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>(i&nbsp;&gt;&nbsp;mid&nbsp;||&nbsp;j&nbsp;&lt;=&nbsp;r&nbsp;&amp;&amp;&nbsp;A[j]&nbsp;&lt;&nbsp;A[i])&nbsp;B[k++]&nbsp;=&nbsp;A[j++]&nbsp;,&nbsp;ans&nbsp;+=&nbsp;mid-i+1;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">else</span>&nbsp;B[k++]&nbsp;=&nbsp;A[i++];<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">for</span>(i=l;i&lt;=r;i++)&nbsp;A[i]&nbsp;=&nbsp;B[i];<br />
}<br />
<span style="color: #0000FF; ">int</span>&nbsp;main()&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;A[maxn]&nbsp;,&nbsp;n;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">while</span>(~scanf("%d"&nbsp;,&nbsp;&amp;n))&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">for</span>(<span style="color: #0000FF; ">int</span>&nbsp;i=0;i&lt;n;i++)&nbsp;cin&nbsp;&gt;&gt;&nbsp;A[i];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ans&nbsp;=&nbsp;0;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;merge_sort(A,&nbsp;0,&nbsp;n-1);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cout&nbsp;&lt;&lt;&nbsp;ans&nbsp;&lt;&lt;&nbsp;endl;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">for(int&nbsp;i=0;i&lt;n;i++)&nbsp;cout&nbsp;&lt;&lt;&nbsp;A[i]&nbsp;&lt;&lt;&nbsp;"&nbsp;";&nbsp;cout&nbsp;&lt;&lt;&nbsp;endl;</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;0;<br />
}</div><img src ="http://www.cppblog.com/JulyRina/aggbug/209956.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/JulyRina/" target="_blank">JulyRina</a> 2015-03-07 15:19 <a href="http://www.cppblog.com/JulyRina/archive/2015/03/07/209956.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>三个入门博弈</title><link>http://www.cppblog.com/JulyRina/archive/2015/03/04/209920.html</link><dc:creator>JulyRina</dc:creator><author>JulyRina</author><pubDate>Wed, 04 Mar 2015 03:16:00 GMT</pubDate><guid>http://www.cppblog.com/JulyRina/archive/2015/03/04/209920.html</guid><wfw:comment>http://www.cppblog.com/JulyRina/comments/209920.html</wfw:comment><comments>http://www.cppblog.com/JulyRina/archive/2015/03/04/209920.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/JulyRina/comments/commentRss/209920.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/JulyRina/services/trackbacks/209920.html</trackback:ping><description><![CDATA[<pre><font color="darkorange"><strong>（一）巴什博奕（Bash Game）：</strong></font>只有一堆n个物品，两个人轮流从这堆物品中取物，规定每次至少取一个，最多取m个。最后取光者得胜。
显然，如果n=m+1，那么由于一次最多只能取m个，所以，无论先取者拿走多少个，后取者都能够一次拿走剩余的物品，后者取胜。因此我们发现了如何取胜的法则：如果n=（m+1）r+s，（r为任意自然数，s&#8804;m),那么先取者要拿走s个物品，如果后取者拿走k（&#8804;m)个，那么先取者再拿走m+1-k个，结果剩下（m+1）（r-1）个，以后保持这样的取法，那么先取者肯定获胜。总之，要保持给对手留下（m+1）的倍数，就能最后获胜。
<font color="blue">即，若n=k*(m+1)，则后取着胜，反之，存在先取者获胜的取法。</font>
<font color="blue"><storng>n%(m+1)==0. 先取者必败。</storng></font>
<font color="darkorange"><strong>（二）威佐夫博奕（Wythoff Game）：</strong></font>有两堆各若干个物品，两个人轮流从某一堆或同时从两堆中取同样多的物品，规定每次至少取一个，多者不限，最后取光者得胜。
这种情况下是颇为复杂的。我们用（ak，bk）（ak &#8804; bk ,k=0，1，2，...,n)表示两堆物品的数量并称其为局势，如果甲面对（0，0），那么甲已经输了，这种局势我们称为奇异局势。前几个奇异局势是：（0，0）、（1，2）、（3，5）、（4，7）、（6，10）、（8，13）、（9，15）、（11，18）、（12，20）。
可以看出,a0=b0=0,ak是未在前面出现过的最小自然数,而 bk= ak + k，奇异局势有
如下三条性质：
1。任何自然数都包含在一个且仅有一个奇异局势中。
由于ak是未在前面出现过的最小自然数，所以有ak &gt; ak-1 ，而 bk= ak + k &gt; ak-1 + k-1 = bk-1 &gt; ak-1 。所以性质1。成立。
2。任意操作都可将奇异局势变为非奇异局势。
事实上，若只改变奇异局势（ak，bk）的某一个分量，那么另一个分量不可能在其他奇异局势中，所以必然是非奇异局势。如果使（ak，bk）的两个分量同时减少，则由于其差不变，且不可能是其他奇异局势的差，因此也是非奇异局势。
3。采用适当的方法，可以将非奇异局势变为奇异局势。
假设面对的局势是（a,b），若 b = a，则同时从两堆中取走 a 个物体，就变为了奇异局势（0，0）；如果a = ak ，b &gt; bk，那么，取走b - bk个物体，即变为奇异局势；如果 a = ak ， b &lt; bk ,则同时从两堆中拿走 ak - ab - ak个物体,变为奇异局势（ ab - ak , ab - ak+ b - ak）；如果a &gt; ak ，b= ak + k,则从第一堆中拿走多余的数量a - ak 即可；如果a &lt; ak ，b= ak + k,分两种情况，第一种，a=aj （j &lt; k）,从第二堆里面拿走 b - bj 即可；第二种，a=bj （j &lt; k）,从第二堆里面拿走 b - aj 即可。
从如上性质可知，两个人如果都采用正确操作，那么面对非奇异局势，先拿者必胜；反之，则后拿者取胜。
那么任给一个局势（a，b），怎样判断它是不是奇异局势呢？我们有如下公式：
ak =[k（1+&#8730;5）/2]，bk= ak + k （k=0，1，2，...,n 方括号表示取整函数)
奇妙的是其中出现了黄金分割数（1+&#8730;5）/2 = 1。618...,因此,由ak，bk组成的矩形近似为黄金矩形，由于2/（1+&#8730;5）=（&#8730;5-1）/2，可以先求出j=[a（&#8730;5-1）/2]，若a=[j（1+&#8730;5）/2]，那么a = aj，bj = aj + j，若不等于，那么a = aj+1，bj+1 = aj+1+ j + 1，若都不是，那么就不是奇异局势。然后再按照上述法则进行，一定会遇到奇异局势。
<font color="darkorange"><strong>（三）尼姆博奕（Nimm Game）：</strong></font>有三堆各若干个物品，两个人轮流从某一堆取任意多的物品，规定每次至少取一个，多者不限，最后取光者得胜。
这种情况最有意思，它与二进制有密切关系，我们用（a，b，c）表示某种局势，首先（0，0，0）显然是奇异局势，无论谁面对奇异局势，都必然失败。第二种奇异局势是（0，n，n），只要与对手拿走一样多的物品，最后都将导致（0，0，0）。仔细分析一下，（1，2，3）也是奇异局势，无论对手如何拿，接下来都可以变为（0，n，n）的情形。
计算机算法里面有一种叫做按位模2加，也叫做异或的运算，我们用符号（^）表示这种运算。这种运算和一般加法不同的一点是1^1=0。先看（1，2，3）的按位模2加的结果：
1 =二进制01
2 =二进制10
3 =二进制11 （^）
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;
0 =二进制00 （注意不进位）
对于奇异局势（0，n，n）也一样，结果也是0。
任何奇异局势（a，b，c）都有a（^）b（^）c =0。
如果我们面对的是一个非奇异局势（a，b，c），要如何变为奇异局势呢？假设 a &lt; b &lt; c,我们只要将 c 变为 a（^）b,即可,因为有如下的运算结果: a（^）b（^）(a（^）b)=(a（^）a)（^）(b（^）b)=0（^）0=0。要将c 变为a（^）b，只要从 c中减去 c-（a（^）b）即可。
<font color="blue">获胜情况对先取者进行讨论：
<strong>异或结果为0，先取者必败，</strong>无获胜方法。后取者获胜；
结果不为0，先取者有获胜的取法。</font>
<font color="red"><strong>拓展：</strong> 任给N堆石子,两人轮流从任一堆中任取(每次只能取自一堆),取最后一颗石子的人获胜，问先取的人如何获胜？</font>
<font color="blue">根据上面所述，N个数异或即可。如果开始的时候T＝0，那么先取者必败，如果开始的时候T&gt;0，那么只要每次取出石子使得T＝0，即先取者有获胜的方法。</font></pre><img src ="http://www.cppblog.com/JulyRina/aggbug/209920.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/JulyRina/" target="_blank">JulyRina</a> 2015-03-04 11:16 <a href="http://www.cppblog.com/JulyRina/archive/2015/03/04/209920.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>多重背包问题</title><link>http://www.cppblog.com/JulyRina/archive/2015/02/18/209834.html</link><dc:creator>JulyRina</dc:creator><author>JulyRina</author><pubDate>Wed, 18 Feb 2015 12:33:00 GMT</pubDate><guid>http://www.cppblog.com/JulyRina/archive/2015/02/18/209834.html</guid><wfw:comment>http://www.cppblog.com/JulyRina/comments/209834.html</wfw:comment><comments>http://www.cppblog.com/JulyRina/archive/2015/02/18/209834.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/JulyRina/comments/commentRss/209834.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/JulyRina/services/trackbacks/209834.html</trackback:ping><description><![CDATA[<h2>题目</h2><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用，每件费用是c[i]，价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量，且价值总和最大。</p><h2>基本算法</h2><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可，因为对于第i种物品有n[i]+1种策略：取0件，取1件&#8230;&#8230;取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值，则有状态转移方程：</p><blockquote style="color: #000000; font-family: Simsun; font-size: medium; line-height: normal;"><p style="margin-top: 11.328125px;"><code>f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0&lt;=k&lt;=n[i]}</code></p></blockquote><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">复杂度是O(V*&#931;n[i])。</p><h2>转化为01背包问题</h2><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">另一种好想好写的基本方法是转化为01背包求解：把第i种物品换成n[i]件01背包中的物品，则得到了物品数为&#931;n[i]的01背包问题，直接求解，复杂度仍然是O(V*&#931;n[i])。</p><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">但是我们期望将它转化为01背包问题之后能够像完全背包一样降低复杂度。仍然考虑二进制的思想，我们考虑把第i种物品换成若干件物品，使得原问题中第i种物品可取的每种策略&#8212;&#8212;取0..n[i]件&#8212;&#8212;均能等价于取若干件代换以后的物品。另外，取超过n[i]件的策略必不能出现。</p><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">方法是：将第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 style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">分成的这几件物品的系数和为n[i]，表明不可能取多于n[i]件的第i种物品。另外这种方法也能保证对于0..n[i]间的每一个整数，均可以用若干个系数的和表示，这个证明可以分0..2^k-1和2^k..n[i]两段来分别讨论得出，并不难，希望你自己思考尝试一下。</p><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">这样就将第i种物品分成了O(log n[i])种物品，将原问题转化为了复杂度为&lt;math&gt;O(V*&#931;log n[i])的01背包问题，是很大的改进。</p><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">下面给出O(log amount)时间处理一件多重背包中物品的过程，其中amount表示物品的数量：</p><pre style="margin-left: 36.421875px; line-height: normal;">procedure MultiplePack(cost,weight,amount)     <br />&nbsp;&nbsp;&nbsp;if cost*amount&gt;=V         <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CompletePack(cost,weight)         <br />&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;ZeroOnePack(k*cost,k*weight)         <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;amount=amount-k         <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;k=k*2     <br />&nbsp;&nbsp;&nbsp;ZeroOnePack(amount*cost,amount*weight) </pre><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">希望你仔细体会这个伪代码，如果不太理解的话，不妨翻译成程序代码以后，单步执行几次，或者头脑加纸笔模拟一下，也许就会慢慢理解了。</p><h2>O(VN)的算法</h2><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">多重背包问题同样有O(VN)的算法。这个算法基于基本算法的状态转移方程，但应用单调队列的方法使每个状态的值可以以均摊O(1)的时间求解。由于用单调队列优化的DP已超出了NOIP的范围，故本文不再展开讲解。我最初了解到这个方法是在楼天成的&#8220;男人八题&#8221;幻灯片上。</p><h2>小结</h2><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">这里我们看到了将一个算法的复杂度由O(V*&#931;n[i])改进到O(V*&#931;log n[i])的过程，还知道了存在应用超出NOIP范围的知识的O(VN)算法。希望你特别注意&#8220;拆分物品&#8221;的思想和方法，自己证明一下它的正确性，并将完整的程序代码写出来。</p><img src ="http://www.cppblog.com/JulyRina/aggbug/209834.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/JulyRina/" target="_blank">JulyRina</a> 2015-02-18 20:33 <a href="http://www.cppblog.com/JulyRina/archive/2015/02/18/209834.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>完全背包问题</title><link>http://www.cppblog.com/JulyRina/archive/2015/02/18/209833.html</link><dc:creator>JulyRina</dc:creator><author>JulyRina</author><pubDate>Wed, 18 Feb 2015 12:31:00 GMT</pubDate><guid>http://www.cppblog.com/JulyRina/archive/2015/02/18/209833.html</guid><wfw:comment>http://www.cppblog.com/JulyRina/comments/209833.html</wfw:comment><comments>http://www.cppblog.com/JulyRina/archive/2015/02/18/209833.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/JulyRina/comments/commentRss/209833.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/JulyRina/services/trackbacks/209833.html</trackback:ping><description><![CDATA[<h2>题目</h2><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">有N种物品和一个容量为V的背包，每种物品都有无限件可用。第i种物品的费用是c[i]，价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量，且价值总和最大。</p><h2>基本思路</h2><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">这个问题非常类似于<a href="http://love-oriented.com/pack/P01.html">01背包问题</a>，所不同的是每种物品有无限件。也就是从每种物品的角度考虑，与它相关的策略已并非取或不取两种，而是有取0件、取1件、取2件&#8230;&#8230;等很多种。如果仍然按照解01背包时的思路，令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程，像这样：</p><blockquote style="color: #000000; font-family: Simsun; font-size: medium; line-height: normal;"><p style="margin-top: 11.328125px;"><code>f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0&lt;=k*c[i]&lt;=v}</code></p></blockquote><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">这跟01背包问题一样有O(VN)个状态需要求解，但求解每个状态的时间已经不是常数了，求解状态f[i][v]的时间是O(v/c[i])，总的复杂度可以认为是O(V*&#931;(V/c[i]))，是比较大的。</p><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">将01背包问题的基本思路加以改进，得到了这样一个清晰的方法。这说明01背包问题的方程的确是很重要，可以推及其它类型的背包问题。但我们还是试图改进这个复杂度。</p><h2>一个简单有效的优化</h2><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">完全背包问题有一个很简单有效的优化，是这样的：若两件物品i、j满足c[i]&lt;=c[j]且w[i]&gt;=w[j]，则将物品j去掉，不用考虑。这个优化的正确性显然：任何情况下都可将价值小费用高得j换成物美价廉的i，得到至少不会更差的方案。对于随机生成的数据，这个方法往往会大大减少物品的件数，从而加快速度。然而这个并不能改善最坏情况的复杂度，因为有可能特别设计的数据可以一件物品也去不掉。</p><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">这个优化可以简单的O(N^2)地实现，一般都可以承受。另外，针对背包问题而言，比较不错的一种方法是：首先将费用大于V的物品去掉，然后使用类似计数排序的做法，计算出费用相同的物品中价值最高的是哪个，可以O(V+N)地完成这个优化。这个不太重要的过程就不给出伪代码了，希望你能独立思考写出伪代码或程序。</p><h2>转化为01背包问题求解</h2><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">既然01背包问题是最基本的背包问题，那么我们可以考虑把完全背包问题转化为01背包问题来解。最简单的想法是，考虑到第i种物品最多选V/c[i]件，于是可以把第i种物品转化为V/c[i]件费用及价值均不变的物品，然后求解这个01背包问题。这样完全没有改进基本思路的时间复杂度，但这毕竟给了我们将完全背包问题转化为01背包问题的思路：将一种物品拆成多件物品。</p><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">更高效的转化方法是：把第i种物品拆成费用为c[i]*2^k、价值为w[i]*2^k的若干件物品，其中k满足<code>c[i]*2^k&lt;=V</code>。这是二进制的思想，因为不管最优策略选几件第i种物品，总可以表示成若干个2^k件物品的和。这样把每种物品拆成O(log V/c[i])件物品，是一个很大的改进。</p><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">但我们有更优的O(VN)的算法。</p><h2>O(VN)的算法</h2><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">这个算法使用一维数组，先看伪代码：</p><pre style="margin-left: 36.421875px; line-height: normal;">for i=1..N     <br />&nbsp;&nbsp;&nbsp;for v=0..V         <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;f[v]=max{f[v],f[v-cost]+weight} </pre><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">你会发现，这个伪代码与<a href="http://love-oriented.com/pack/P01.html">P01</a>的伪代码只有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 style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">值得一提的是，上面的伪代码中两层for循环的次序可以颠倒。这个结论有可能会带来算法时间常数上的优化。</p><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">这个算法也可以以另外的思路得出。例如，将基本思路中求解f[i][v-c[i]]的状态转移方程显式地写出来，代入原方程中，会发现该方程可以等价地变形成这种形式：</p><blockquote style="color: #000000; font-family: Simsun; font-size: medium; line-height: normal;"><p style="margin-top: 11.328125px;"><code>f[i][v]=max{f[i-1][v],f[i][v-c[i]]+w[i]}</code></p></blockquote><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">将这个方程用一维数组实现，便得到了上面的伪代码。</p><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">最后抽象出处理一件完全背包类物品的过程伪代码：</p><pre style="margin-left: 36.421875px; line-height: normal;">procedure CompletePack(cost,weight)     <br />&nbsp;&nbsp;&nbsp;for v=cost..V         <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;f[v]=max{f[v],f[v-c[i]]+w[i]} </pre><h2>总结</h2><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">完全背包问题也是一个相当基础的背包问题，它有两个状态转移方程，分别在&#8220;基本思路&#8221;以及&#8220;O(VN)的算法&#8220;的小节中给出。希望你能够对这两个状态转移方程都仔细地体会，不仅记住，也要弄明白它们是怎么得出来的，最好能够自己想一种得到这些方程的方法。事实上，对每一道动态规划题目都思考其方程的意义以及如何得来，是加深对动态规划的理解、提高动态规划功力的好方法。</p><img src ="http://www.cppblog.com/JulyRina/aggbug/209833.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/JulyRina/" target="_blank">JulyRina</a> 2015-02-18 20:31 <a href="http://www.cppblog.com/JulyRina/archive/2015/02/18/209833.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>01背包问题</title><link>http://www.cppblog.com/JulyRina/archive/2015/02/18/209832.html</link><dc:creator>JulyRina</dc:creator><author>JulyRina</author><pubDate>Wed, 18 Feb 2015 12:30:00 GMT</pubDate><guid>http://www.cppblog.com/JulyRina/archive/2015/02/18/209832.html</guid><wfw:comment>http://www.cppblog.com/JulyRina/comments/209832.html</wfw:comment><comments>http://www.cppblog.com/JulyRina/archive/2015/02/18/209832.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/JulyRina/comments/commentRss/209832.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/JulyRina/services/trackbacks/209832.html</trackback:ping><description><![CDATA[<h2>题目</h2><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">有N件物品和一个容量为V的背包。第i件物品的费用是c[i]，价值是w[i]。求解将哪些物品装入背包可使价值总和最大。</p><h2>基本思路</h2><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">这是最基础的背包问题，特点是：每种物品仅有一件，可以选择放或不放。</p><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">用子问题定义状态：即f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。则其状态转移方程便是：</p><blockquote style="color: #000000; font-family: Simsun; font-size: medium; line-height: normal;"><p style="margin-top: 11.328125px;"><code>f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}</code></p></blockquote><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">这个方程非常重要，基本上所有跟背包相关的问题的方程都是由它衍生出来的。所以有必要将它详细解释一下：&#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><h2>优化空间复杂度</h2><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">以上方法的时间和空间复杂度均为O(VN)，其中时间复杂度应该已经不能再优化了，但空间复杂度却可以优化到O。</p><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">先考虑上面讲的基本思路如何实现，肯定是有一个主循环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><pre style="margin-left: 36.421875px; line-height: normal;">for i=1..N     <br />&nbsp;&nbsp;&nbsp;for v=V..0         <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;f[v]=max{f[v],f[v-c[i]]+w[i]}; </pre><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">其中的f[v]=max{f[v],f[v-c[i]]}一句恰就相当于我们的转移方程<code>f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]}</code>，因为现在的f[v-c[i]]就相当于原来的f[i-1][v-c[i]]。如果将v的循环顺序从上面的逆序改成顺序的话，那么则成了f[i][v]由f[i][v-c[i]]推知，与本题意不符，但它却是另一个重要的背包问题<a href="http://love-oriented.com/pack/P02.html">P02</a>最简捷的解决方案，故学习只用一维数组解01背包问题是十分必要的。</p><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">事实上，使用一维数组解01背包的程序在后面会被多次用到，所以这里抽象出一个处理一件01背包中的物品过程，以后的代码中直接调用不加说明。</p><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">过程ZeroOnePack，表示处理一件01背包中的物品，两个参数cost、weight分别表明这件物品的费用和价值。</p><pre style="margin-left: 36.421875px; line-height: normal;">procedure ZeroOnePack(cost,weight)     <br />&nbsp;&nbsp;&nbsp;for v=V..cost         <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;f[v]=max{f[v],f[v-cost]+weight} </pre><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">注意这个过程里的处理与前面给出的伪代码有所不同。前面的示例程序写成v=V..0是为了在程序中体现每个状态都按照方程求解了，避免不必要的思维复杂度。而这里既然已经抽象成看作黑箱的过程了，就可以加入优化。费用为cost的物品不会影响状态f[0..cost-1]，这是显然的。</p><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">有了这个过程以后，01背包问题的伪代码就可以这样写：</p><pre style="margin-left: 36.421875px; line-height: normal;">for i=1..N     <br />&nbsp;&nbsp;&nbsp;ZeroOnePack(c[i],w[i]); </pre><h2>初始化的细节问题</h2><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">我们看到的求最优解的背包问题题目中，事实上有两种不太相同的问法。有的题目要求&#8220;恰好装满背包&#8221;时的最优解，有的题目则并没有要求必须把背包装满。一种区别这两种问法的实现方法是在初始化的时候有所不同。</p><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">如果是第一种问法，要求恰好装满背包，那么在初始化时除了f[0]为0其它f[1..V]均设为-&#8734;，这样就可以保证最终得到的f[N]是一种恰好装满背包的最优解。</p><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">如果并没有要求必须把背包装满，而是只希望价格尽量大，初始化时应该将f[0..V]全部设为0。</p><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">为什么呢？可以这样理解：初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满，那么此时只有容量为0的背包可能被价值为0的nothing&#8220;恰好装满&#8221;，其它容量的背包均没有合法的解，属于未定义的状态，它们的值就都应该是-&#8734;了。如果背包并非必须被装满，那么任何容量的背包都有一个合法解&#8220;什么都不装&#8221;，这个解的价值为0，所以初始时状态的值也就全部为0了。</p><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">这个小技巧完全可以推广到其它类型的背包问题，后面也就不再对进行状态转移之前的初始化进行讲解。</p><h2>一个常数优化</h2><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">前面的伪代码中有 for v=V..1，可以将这个循环的下限进行改进。</p><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">由于只需要最后f[v]的值，倒推前一个物品，其实只要知道f[v-w[n]]即可。以此类推，对以第j个背包，其实只需要知道到f[v-sum{w[j..n]}]即可，即代码中的</p><pre style="margin-left: 36.421875px; line-height: normal;">for i=1..N     <br />&nbsp;&nbsp;&nbsp;for v=V..0 </pre><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">可以改成</p><pre style="margin-left: 36.421875px; line-height: normal;">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 </pre><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">这对于V比较大时是有用的。</p><h2>小结</h2><p style="margin-top: 12.140625px; font-family: Simsun; font-size: medium; line-height: normal;">01背包问题是最基本的背包问题，它包含了背包问题中设计状态、方程的最基本思想，另外，别的类型的背包问题往往也可以转换成01背包问题求解。故一定要仔细体会上面基本思路的得出方法，状态转移方程的意义，以及最后怎样优化的空间复杂度。</p><img src ="http://www.cppblog.com/JulyRina/aggbug/209832.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/JulyRina/" target="_blank">JulyRina</a> 2015-02-18 20:30 <a href="http://www.cppblog.com/JulyRina/archive/2015/02/18/209832.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>