﻿<?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++博客-&lt;b&gt;&lt;font color=#ff0000&gt;清木@_@逍遥&lt;/font&gt;&lt;/b&gt;-随笔分类-Algorithms</title><link>http://www.cppblog.com/yuqilin1228/category/13384.html</link><description>路在脚下</description><language>zh-cn</language><lastBuildDate>Fri, 26 Mar 2010 14:12:43 GMT</lastBuildDate><pubDate>Fri, 26 Mar 2010 14:12:43 GMT</pubDate><ttl>60</ttl><item><title>【转载】并查集 (Union-Find Sets)</title><link>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110618.html</link><dc:creator>LynnRaymond</dc:creator><author>LynnRaymond</author><pubDate>Fri, 26 Mar 2010 13:59:00 GMT</pubDate><guid>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110618.html</guid><wfw:comment>http://www.cppblog.com/yuqilin1228/comments/110618.html</wfw:comment><comments>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110618.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yuqilin1228/comments/commentRss/110618.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yuqilin1228/services/trackbacks/110618.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 由于时间太久忘记出处了，不好意思...找到后补上.并查集 (Union-Find Sets)并查集： (union-find sets)是一种简单的用途广泛的集合. 并查集是若干个不相交集合，能够实现较快的合并和判断元素所在集合的操作，应用很多。一般采取树形结构来存储并查集，并利用一个rank数组来存储集合的深度下界，在查找操作时进行路径压缩使后续的查找操作加速。这样优化实现的...&nbsp;&nbsp;<a href='http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110618.html'>阅读全文</a><img src ="http://www.cppblog.com/yuqilin1228/aggbug/110618.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yuqilin1228/" target="_blank">LynnRaymond</a> 2010-03-26 21:59 <a href="http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110618.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载】从零开始学算法：十种排序算法介绍</title><link>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110617.html</link><dc:creator>LynnRaymond</dc:creator><author>LynnRaymond</author><pubDate>Fri, 26 Mar 2010 13:54:00 GMT</pubDate><guid>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110617.html</guid><wfw:comment>http://www.cppblog.com/yuqilin1228/comments/110617.html</wfw:comment><comments>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110617.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yuqilin1228/comments/commentRss/110617.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yuqilin1228/services/trackbacks/110617.html</trackback:ping><description><![CDATA[<a title="Permanent link to 从零开始学算法：十种排序算法介绍（上）" href="http://www.matrix67.com/blog/archives/166" rel="bookmark"><u><font color="#800080">从零开始学算法：十种排序算法介绍（上）</font></u></a>&nbsp;&nbsp;
From:Martix67【牛啊！】<br>今天我正式开始按照<a href="http://www.matrix67.com/blog/article.asp?id=188" target="_blank"><u><font color="#0000ff">我的目录</font></u></a>写我的OI心得了。我要把
我所有学到的OI知识传给以后千千万万的OIer。以前写过的一些东西不重复写了，但我最后将会重新整理，使之成为一个完整的教程。<br>&nbsp;&nbsp;&nbsp;&nbsp;按照
我的目录，讲任何东西之前我都会先介绍时间复杂度的相关知识，以后动不动就会扯到这个东西。这个已经写过了，你可以在<a href="http://www.matrix67.com/blog/article.asp?id=134" target="_blank"><u><font color="#0000ff">这里</font></u></a>看到那篇又臭又长的文章。在讲
排序算法的过程中，我们将始终围绕时间复杂度的内容进行说明。<br>&nbsp;&nbsp;&nbsp;&nbsp;我把这篇文章称之为&#8220;从零开始学算法&#8221;，因为排序算法是最基础的算法，介绍
算法时从各种排序算法入手是最好不过的了。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;给出n个数，怎样将它们从小到大排序？下面一口气讲三种常用的算法，它们是最简单的、
最显然的、最容易想到的。选择排序(Selection
Sort)是说，每次从数列中找出一个最小的数放到最前面来，再从剩下的n-1个数中选择一个最小的，不断做下去。插入排序(Insertion
Sort)是，每次从数列中取一个还没有取出过的数，并按照大小关系插入到已经取出的数中使得已经取出的数仍然有序。冒泡排序(Bubble
Sort)分为若干趟进行，每一趟排序从前往后比较每两个相邻的元素的大小（因此一趟排序要比较n-1对位置相邻的数）并在每次发现前面的那个数比紧接它
后的数大时交换位置；进行足够多趟直到某一趟跑完后发现这一趟没有进行任何交换操作（最坏情况下要跑n-1趟，这种情况在最小的数位于给定数列的最后面时
发生）。事实上，在第一趟冒泡结束后，最后面那个数肯定是最大的了，于是第二次只需要对前面n-1个数排序，这又将把这n-1个数中最小的数放到整个数列
的倒数第二个位置。这样下去，冒泡排序第i趟结束后后面i个数都已经到位了，第i+1趟实际上只考虑前n-i个数（需要的比较次数比前面所说的n-1要
小）。这相当于用数学归纳法证明了冒泡排序的正确性：实质与选择排序相同。上面的三个算法描述可能有点模糊了，没明白的话网上找资料，代码和动画演示遍地
都是。<br><img alt="" src="http://www.matrix67.com/blogimage/200703311.gif" border="0"><br>&nbsp;&nbsp;&nbsp;&nbsp;这
三种算法非常容易理解，因为我们生活当中经常在用。比如，班上的MM搞选美活动，有人叫我给所有MM排个名。我们通常会用选择排序，即先找出自己认为最漂
亮的，然后找第二漂亮的，然后找第三漂亮的，不断找剩下的人中最满意的。打扑克牌时我们希望抓完牌后手上的牌是有序的，三个8挨在一起，后面紧接着两个
9。这时，我们会使用插入排序，每次拿到一张牌后把它插入到手上的牌中适当的位置。什么时候我们会用冒泡排序呢？比如，体育课上从矮到高排队时，站队完毕
后总会有人出来，比较挨着的两个人的身高，指挥到：你们俩调换一下，你们俩换一下。<br>&nbsp;&nbsp;&nbsp;&nbsp;这是很有启发性的。这告诉我们，什么时候用什么排序最
好。当人们渴望先知道排在前面的是谁时，我们用选择排序；<strong style="color: #0000ff;">当我们不断拿
到新的数并想保持已有的数始终有序时，我们用插入排序；当给出的数列已经比较有序，只需要小幅度的调整一下时，我们用冒泡排序。 <br><br>&nbsp;&nbsp;&nbsp;&nbsp;我
们来算一下最坏情况下三种算法各需要多少次比较和赋值操作。<br>&nbsp;&nbsp;&nbsp;&nbsp;选择排序在第i次选择时赋值和比较都需要n-i次（在n-i+1个数中选一个
出来作为当前最小值，其余n-i个数与当前最小值比较并不断更新当前最小值），然后需要一次赋值操作。总共需要n(n-1)/2次比较与n(n-1)
/2+n次赋值。<br>&nbsp;&nbsp;&nbsp;&nbsp;插入排序在第i次寻找插入位置时需要最多i-1次比较（从后往前找到第一个比待插入的数小的数，最坏情况发生在这个数是
所有已经取出的数中最小的一个的时候），在已有数列中给新的数腾出位置需要i-1次赋值操作来实现，还需要两次赋值借助临时变量把新取出的数搬进搬出。也
就是说，最坏情况下比较需要n(n-1)/2次，赋值需要n(n-1)/2+2n次。我这么写有点误导人，大家不要以为程序的实现用了两个数组哦，其实一
个数组就够了，看看上面的演示就知道了。我只说算法，一般不写如何实现。学算法的都是强人，知道算法了都能写出一个漂亮的代码来。<br>&nbsp;&nbsp;&nbsp;&nbsp;冒泡排
序第i趟排序需要比较n-i次，n-1趟排序总共n(n-1)/2次。给出的序列逆序排列是最坏的情况，这时每一次比较都要进行交换操作。一次交换操作需
要3次赋值实现，因此冒泡排序最坏情况下需要赋值3n(n-1)/2次。<br>&nbsp;&nbsp;&nbsp;&nbsp;按照渐进复杂度理论，忽略所有的常数，三种排序的最坏情况下复杂
度都是一样的：O(n^2)。但实际应用中三种排序的效率并不相同。实践证明（政治考试时每道大题都要用这四个字），插入排序是最快的（虽然最坏情况下与
选择排序相当甚至更糟），因为每一次插入时寻找插入的位置多数情况只需要与已有数的一部分进行比较（你可能知道这还能二分）。你或许会说冒泡排序也可以在
半路上完成，还没有跑到第n-1趟就已经有序。但冒泡排序的交换操作更费时，而插入排序中找到了插入的位置后移动操作只需要用赋值就能完成（你可能知道这
还能用move）。本文后面将介绍的一种算法就利用插入排序的这些优势。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;我们证明了，三种排序方法在最坏情况下时间复杂度都是
O(n^2)。但大家想过吗，这只是最坏情况下的。在很多时候，复杂度没有这么大，因为插入和冒泡在数列已经比较有序的情况下需要的操作远远低于n^2次
（最好情况下甚至是线性的）。抛开选择排序不说（因为它的复杂度是&#8220;死&#8221;的，对于选择排序没有什么&#8220;好&#8221;的情况），我们下面探讨插入排序和冒泡排序在特定
数据和平均情况下的复杂度。<br>&nbsp;&nbsp;&nbsp;&nbsp;你会发现，如果把插入排序中的移动赋值操作看作是把当前取出的元素与前面取出的且比它大的数逐一交换，那插入
排序和冒泡排序对数据的变动其实都是相邻元素的交换操作。下面我们说明，若只能对数列中相邻的数进行交换操作，如何计算使得n个数变得有序最少需要的交换
次数。<br>&nbsp;&nbsp;&nbsp;&nbsp;我们定义逆序对的概念。假设我们要把数列从小到大排序，一个逆序对是指的在原数列中，左边的某个数比右边的大。也就是说，如果找到
了某个i和j使得i&lt;j且Ai&gt;Aj，我们就说我们找到了一个逆序对。比如说，数列3,1,4,2中有三个逆序对，而一个已经有序的数列逆序
对个数为0。我们发现，交换两个相邻的数最多消除一个逆序对，且冒泡排序（或插入排序）中的一次交换恰好能消除一个逆序对。那么显然，原数列中有多少个逆
序对冒泡排序（或插入排序）就需要多少次交换操作，这个操作次数不可能再少。<br>&nbsp;&nbsp;&nbsp;&nbsp;若给出的n个数中有m个逆序对，插入排序的时间复杂度可以说
是O(m+n)的，而冒泡排序不能这么说，因为冒泡排序有很多&#8220;无用&#8221;的比较（比较后没有交换），这些无用的比较超过了O(m+n)个。从这个意义上说，
插入排序仍然更为优秀，因为冒泡排序的复杂度要受到它跑的趟数的制约。一个典型的例子是这样的数列：8, 2, 3, 4, 5, 6, 7,
1。在这样的输入数据下插入排序的优势非常明显，冒泡排序只能哭着喊上天不公。<br>&nbsp;&nbsp;&nbsp;&nbsp;然而，我们并不想计算排序算法对于某个特定数据的效率。我
们真正关心的是，对于所有可能出现的数据，算法的平均复杂度是多少。不用激动了，平均复杂度并不会低于平方。下面证明，两种算法的平均复杂度仍然是
O(n^2)的。<br>&nbsp;&nbsp;&nbsp;&nbsp;我们仅仅证明算法需要的交换次数平均为O(n^2)就足够了。前面已经说过，它们需要的交换次数与逆序对的个数相同。我
们将证明，n个数的数列中逆序对个数平均O(n^2)个。<br>&nbsp;&nbsp;&nbsp;&nbsp;计算的方法是十分巧妙的。如果把给出的数列反过来（从后往前倒过来写），你会发
现原来的逆序对现在变成顺序的了，而原来所有的非逆序对现在都成逆序了。正反两个数列的逆序对个数加起来正好就是数列所有数对的个数，它等于n(n-
1)/2。于是，平均每个数列有n(n-1)/4个逆序对。忽略常数，逆序对平均个数O(n^2)。<br>&nbsp;&nbsp;&nbsp;&nbsp;上面的讨论启示我们，要想搞出一个复
杂度低于平方级别的排序算法，我们需要想办法能把离得老远的两个数进行操作。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;人们想啊想啊想啊，怎么都想不出怎样才能搞出复杂度
低于平方的算法。后来，英雄出现了，Donald Shell发明了一种新的算法，我们将证明它的复杂度最坏情况下也没有O(n^2)
（似乎有人不喜欢研究正确性和复杂度的证明，我会用实例告诉大家，这些证明是非常有意思的）。他把这种算法叫做Shell增量排序算法（大家常说的希尔排
序）。<br>&nbsp;&nbsp;&nbsp;&nbsp;Shell排序算法依赖一种称之为&#8220;排序增量&#8221;的数列，不同的增量将导致不同的效率。假如我们对20个数进行排序，使用的增量为
1,3,7。那么，我们首先对这20个数进行&#8220;7-排序&#8221;(7-sortedness)。所谓7-排序，就是按照位置除以7的余数分组进行排序。具体地
说，我们将把在1、8、15三个位置上的数进行排序，将第2、9、16个数进行排序，依此类推。这样，对于任意一个数字k，单看A(k),
A(k+7), A(k+14),
&#8230;这些数是有序的。7-排序后，我们接着又进行一趟3-排序（别忘了我们使用的排序增量为1,3,7）。最后进行1-排序（即普通的排序）后整个
Shell算法完成。看看我们的例子：<br><br>&nbsp;&nbsp;3 7 9 0 5 1 6
8 4 2 0 6 1 5 7 3 4 9 8 2&nbsp;&nbsp;&lt;-- 原数列<br>&nbsp;&nbsp;3 3 2 0 5 1 5 7 4 4 0 6 1 6 8
7 9 9 8 2&nbsp;&nbsp;&lt;-- 7-排序后<br>&nbsp;&nbsp;0 0 1 1 2 2 3 3 4 4 5 6 5 6 8 7 7 9 8
9&nbsp;&nbsp;&lt;-- 3-排序后<br>&nbsp;&nbsp;0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9&nbsp;&nbsp;&lt;--
1-排序后（完成）<br><br>&nbsp;&nbsp;&nbsp;&nbsp;在每一趟、每一组的排序中我们总是使用插入排序。仔细观察上面的例子你会发现是什么导致了
Shell排序的高效。对，每一趟排序将使得数列部分有序，从而使得以后的插入排序很快找到插入位置。我们下面将紧紧围绕这一点来证明Shell排序算法
的时间复杂度上界。<br>&nbsp;&nbsp;&nbsp;&nbsp;只要排序增量的第一个数是1，Shell排序算法就是正确的。但是不同的增量将导致不同的时间复杂度。我们上面例子中
的增量(1, 3, 7, 15, 31, &#8230;,
2^k-1)是使用最广泛的增量序列之一，可以证明使用这个增量的时间复杂度为O(n&#8730;n)。这个证明很简单，大家可以参看一些其它的资料，我们今天不证
明它。今天我们证明，使用增量1, 2, 3, 4, 6, 8, 9, 12, 16, &#8230;, 2^p*3^q，时间复杂度为O(n*(log
n)^2)。<br>&nbsp;&nbsp;&nbsp;&nbsp;很显然，任何一个大于1的正整数都可以表示为2x+3y，其中x和y是非负整数。于是，如果一个数列已经是2-排序的且是
3-排序的，那么对于此时数列中的每一个数A(i)，它的左边比它大的只有可能是A(i-1)。A2绝对不可能比A12大，因为10可以表示为两个2和两
个3的和，则A2&lt;A4&lt;A6&lt;A9&lt;A12。那么，在这个增量中的1-排序时每个数找插入位置只需要比较一次。一共有n个数，
所以1-排序是O(n)的。事实上，这个增量中的2-排序也是O(n)，因为在2-排序之前，这个数列已经是4-排序且6-排序过的，只看数列的奇数项或
者偶数项（即单看每一组）的话就又成了刚才的样子。这个增量序列巧妙就巧妙在，如果我们要进行h-排序，那么它一定是2h-排序过且3h-排序过，于是处
理每个数A(i)的插入时就只需要和A(i-h)进行比较。这个结论对于最开始几次（h值较大时）的h-排序同样成立，当2h、3h大于n时，按照定义，
我们也可以认为数列是2h-排序和3h-排序的，这并不影响上述结论的正确性（你也可以认为h太大以致于排序时每一组里的数字不超过3个，属于常数级）。
现在，这个增量中的每一趟排序都是O(n)的，我们只需要数一下一共跑了多少趟。也就是说，我们现在只需要知道小于n的数中有多少个数具有2^p*3^q
的形式。要想2^p*3^q不超过n，p的取值最多O(log n)个，q的取值最多也是O(log
n)个，两两组合的话共有O(logn*logn)种情况。于是，这样的增量排序需要跑O((log
n)^2)趟，每一趟的复杂度O(n)，总的复杂度为O(n*(log n)^2)。早就说过了，证明时间复杂度其实很有意思。<br>&nbsp;&nbsp;&nbsp;&nbsp;我们自然
会想，有没有能使复杂度降到O(nlogn)甚至更低的增量序列。很遗憾，现在没有任何迹象表明存在O(nlogn)的增量排序。但事实上，很多时候
Shell排序的实际效率超过了O(nlogn)的排序算法。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;后面我们将介绍三种O(nlogn)的排序算法和三种线性时间的排
序算法。最后我们将以外部排序和排序网络结束这一章节。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;很多人问到我关于转贴的问题。我欢迎除商业目的外任何形式的转贴（论坛、
Blog、Wiki、个人网站、PodCast，甚至做成ppt、pdf），但一定要注明出处，最好保留原始链接。我的网站需要点反向链接才能在网络中生
存下去，大家也都可以关注并且推广这个Blog。我一直支持cc版权协议，因此发现了文章中的问题或者想要补充什么东西尽管提出来，好让更多的人学习到好
东西。我昨天看Blog上原来写的一些东西，居然连着发现了几个错误式子和错别字，好奇大家居然没有提出来。发现了问题真的要告诉我，即使格式有点问题也
要说一下，决不能让它那么错着。另外有什么建议或想法也请说一下，我希望听到不同的声音不同的见解，好让我决定这类文章以后的发展方向。<br><br>Matrix67
原创<br>转贴请注明出处<br><br><a  href="http://www.matrix67.com/blog/archives/166" title="Permanent link to
从零开始学算法：十种排序算法介绍（上）" rel="bookmark"><u><font color="#800080">从零开始学算法：十种排序算法介绍（中）</font></u></a><br>&nbsp;本文被华丽的分割线分为了四段。对
于O(nlogn)的排序算法，我们详细介绍归并排序并证明归并排序的时间复杂度，然后简单介绍堆排序，之后给出快速排序的基本思想和复杂度证明。最后我
们将证明，O(nlogn)在理论上已经达到了最优。学过OI的人一般都学过这些很基础的东西，大多数OIer们不必看了。为了保持系列文章的完整性，我
还是花时间写了一下。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;首先考虑一个简单的问题：如何在线性的时间内将两个有序队列合并为一个有序队列（并输出）？<br><br>A
队列：1 3 5 7 9<br>B队列：1 2 7 8 9<br><br>&nbsp;&nbsp;&nbsp;&nbsp;看上面的例子，AB两个序列都是已经有序的了。在给出数据已经有序的情况下，我
们会发现很多神奇的事，比如，我们将要输出的第一个数一定来自于这两个序列各自最前面的那个数。两个数都是1，那么我们随便取出一个（比如A队列的那个
1）并输出：<br><br>A队列：<s>1</s> 3 5 7 9<br>B队列：1
2 7 8 9<br>输出：1<br><br>&nbsp;&nbsp;&nbsp;&nbsp;注意，我们取出了一个数，在原数列中删除这个数。删除操作是通过移动队首指针实
现的，否则复杂度就高了。<br>&nbsp;&nbsp;&nbsp;&nbsp;现在，A队列打头的数变成3了，B队列的队首仍然是1。此时，我们再比较3和1哪个大并输出小的那个数：<br><br>A队列：<s>1</s> 3
5 7 9<br>B队列：<s>1</s> 2 7 8 9<br>输出：1 1<br><br>&nbsp;&nbsp;&nbsp;&nbsp;接
下来的几步如下：<br><br>A队列：<s>1</s> 3 5 7
9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A队列：<s>1 3</s> 5 7 9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A队列：<s>1 3 5</s> 7
9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;A队列：<s>1 3 5 7</s> 9<br>B队列：<s>1 2</s> 7 8 9&nbsp;&nbsp; ==&gt;&nbsp;&nbsp; B队列：<s>1
2</s> 7 8 9&nbsp;&nbsp; ==&gt;&nbsp;&nbsp; B队列：<s>1 2</s> 7 8 9&nbsp;&nbsp;&nbsp;&nbsp;==&gt;&nbsp;&nbsp; B队列：<s>1 2</s> 7
8 9&nbsp;&nbsp;&nbsp;&nbsp; &#8230;&#8230;<br>输出：1 1 2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;输出：1 1 2 3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;输出：1 1 2 3
5&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 输出：1 1 2 3 5 7<br><br>&nbsp;&nbsp;&nbsp;&nbsp;我希望你明白了这是怎么做的。这个做法显然是正确的，复杂
度显然是线性。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;归并排序(Merge
Sort)将会用到上面所说的合并操作。给出一个数列，归并排序利用合并操作在O(nlogn)的时间内将数列从小到大排序。归并排序用的是分治
(Divide and
Conquer)的思想。首先我们把给出的数列平分为左右两段，然后对两段数列分别进行排序，最后用刚才的合并算法把这两段（已经排过序的）数列合并为一
个数列。有人会问&#8220;对左右两段数列分别排序时用的什么排序&#8221;么？答案是：用归并排序。也就是说，我们递归地把每一段数列又分成两段进行上述操作。你不需要
关心实际上是怎么操作的，我们的程序代码将递归调用该过程直到数列不能再分（只有一个数）为止。<br>&nbsp;&nbsp;&nbsp;&nbsp;初看这个算法时有人会误以为时间复杂度相
当高。我们下面给出的一个图将用非递归的眼光来看归并排序的实际操作过程，供大家参考。我们可以借助这个图证明，归并排序算法的时间复杂度为
O(nlogn)。<br><br>[3] [1] [4] [1] [5] [9]
[2] [7]<br>&nbsp;&nbsp;\ /&nbsp;&nbsp;&nbsp;&nbsp; \ /&nbsp;&nbsp;&nbsp;&nbsp; \ /&nbsp;&nbsp;&nbsp;&nbsp; \ /<br>[1 3]&nbsp;&nbsp; [1 4]&nbsp;&nbsp; [5 9]&nbsp;&nbsp; [2
7]<br>&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp; /&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp; /<br>&nbsp;&nbsp; [1 1 3 4]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [2 5 7 9]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
\&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [1 1 2 3 4 5 7 9]<br><br>&nbsp;&nbsp;&nbsp;&nbsp;上图中的每一个&#8220; \ /
&#8221;表示的是上文所述的线性时间合并操作。上图用了4行来图解归并排序。如果有n个数，表示成上图显然需要O(logn)行。每一行的合并操作复杂度总和都
是O(n)，那么logn行的总复杂度为O(nlogn)。这相当于用递归树的方法对归并排序的复杂度进行了分析。假设，归并排序的复杂度为
T(n)，T(n)由两个T(n/2)和一个关于n的线性时间组成，那么T(n)=2*T(n/2)+O(n)。不断展开这个式子我们可以同样可以得到
T(n)=O(nlogn)的结论，你可以自己试试。如果你能在线性的时间里把分别计算出的两组不同数据的结果合并在一起，根据
T(n)=2*T(n/2)+O(n)=O(nlogn)，那么我们就可以构造O(nlogn)的分治算法。这个结论后面经常用。我们将在计算几何部分举
一大堆类似的例子。<br>&nbsp;&nbsp;&nbsp;&nbsp;如果你第一次见到这么诡异的算法，你可能会对<a href="http://www.matrix67.com/blog/article.asp?id=29" target="_blank"><strong> <font color="#618898">这个</font></strong> </a>感兴趣。
分治是递归的一种应用。这是我们第一次接触递归运算。下面说的快速排序也是用的递归的思想。递归程序的复杂度分析通常和上面一样，主定理(Master
Theory)可以简化这个分析过程。主定理和本文内容离得太远，我们以后也不会用它，因此我们不介绍它，大家可以自己去查。有个名词在这里的话找学习资
料将变得非常容易，我最怕的就是一个东西不知道叫什么名字，半天找不到资料。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;归并排序有一个有趣的副产品。利用归并排序能够在
O(nlogn)的时间里计算出给定序列里逆序对的个数。你可以用任何一种平衡二叉树来完成这个操作，但用归并排序统计逆序对更方便。我们讨论逆序对一般
是说的一个排列中的逆序对，因此这里我们假设所有数不相同。假如我们想要数1, 6, 3, 2, 5,
4中有多少个逆序对，我们首先把这个数列分为左右两段。那么一个逆序对只可能有三种情况：两个数都在左边，两个数都在右边，一个在左一个在右。在左右两段
分别处理完后，线性合并的过程中我们可以顺便算出所有第三种情况的逆序对有多少个。换句话说，我们能在线性的时间里统计出A队列的某个数比B队列的某个数
大有多少种情况。<br><br>A队列：1 3 6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A队列：<s>1</s>
3 6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A队列：<s>1</s> 3 6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A队列：<s>1 3</s> 6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A队列：<s>1 3</s>
6<br>B队列：2 4 5&nbsp;&nbsp; ==&gt;&nbsp;&nbsp; B队列：2 4 5&nbsp;&nbsp; ==&gt;&nbsp;&nbsp; B队列：<s>2</s> 4 5&nbsp;&nbsp;
==&gt;&nbsp;&nbsp; B队列：<s>2</s> 4 5&nbsp;&nbsp; ==&gt;&nbsp;&nbsp; B队列：<s>2 4</s> 5&nbsp;&nbsp; &#8230;&#8230;<br>输
出：&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 输出：1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;输出：1 2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;输出：1 2
3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;输出：1 2 3 4<br><br>&nbsp;&nbsp;&nbsp;&nbsp;每一次从B队列取出一个数时，我们就知道了在A队列中有多少个数比B
队列的这个数大，它等于A队列现在还剩的数的个数。比如，当我们从B队列中取出2时，我们同时知道了A队列的3和6两个数比2大。在合并操作中我们不断更
新A队列中还剩几个数，在每次从B队列中取出一个数时把当前A队列剩的数目加进最终答案里。这样我们算出了所有&#8220;大的数在前一半，小的数在后一半&#8221;的情
况，其余情况下的逆序对在这之前已经被递归地算过了。<br><br>============================华丽的分割
线============================<br><br>&nbsp;&nbsp;&nbsp;&nbsp;堆排序(Heap Sort)利用了堆(Heap)这种数据结构（<a href="http://www.matrix67.com/blog/article.asp?id=115" target="_blank"><strong> <font color="#618898">什么是堆？</font></strong> </a>）。
堆的插入操作是平均常数的，而删除一个根节点需要花费O(log
n)的时间。因此，完成堆排序需要线性时间建立堆（把所有元素依次插入一个堆），然后用总共O(nlogn)的时间不断取出最小的那个数。只要堆会搞，堆
排序就会搞。堆在那篇日志里有详细的说明，因此这里不重复说了。<br><br>============================华丽的分割
线============================<br><br>&nbsp;&nbsp;&nbsp;&nbsp;快速排序(Quick
Sort)也应用了递归的思想。我们想要把给定序列分成两段，并对这两段分别进行排序。一种不错的想法是，选取一个数作为&#8220;关键字&#8221;，并把其它数分割为两
部分，把所有小于关键字的数都放在关键字的左边，大于关键字的都放在右边，然后递归地对左边和右边进行排序。把该区间内的所有数依次与关键字比较，我们就
可以在线性的时间里完成分割的操作。完成分割操作有很多有技巧性的实现方法，比如最常用的一种是定义两个指针，一个从前往后找找到比关键字大的，一个从后
往前找到比关键字小的，然后两个指针对应的元素交换位置并继续移动指针重复刚才的过程。这只是大致的方法，具体的实现还有很多细节问题。快速排序是我们最
常用的代码之一，网上的快速排序代码五花八门，各种语言，各种风格的都有。大家可以随便找一个来看看，我说过了我们讲算法但不讲如何实现。NOIp很简
单，很多人NOIp前就背了一个快速排序代码就上战场了。当时我把快速排序背完了，抓紧时间还顺便背了一下历史，免得晚上听写又不及格。<br>&nbsp;&nbsp;&nbsp;&nbsp;不
像归并排序，快速排序的时间复杂度很难计算。我们可以看到，归并排序的复杂度最坏情况下也是O(nlogn)的，而快速排序的最坏情况是O(n^2)的。
如果每一次选的关键字都是当前区间里最大（或最小）的数，那么这样将使得每一次的规模只减小一个数，这和插入排序、选择排序等平方级排序没有区别。这种情
况不是不可能发生。如果你每次选择关键字都是选择的该区间的第一个数，而给你的数据恰好又是已经有序的，那你的快速排序就完蛋了。显然，最好情况是每一次
选的数正好就是中位数，这将把该区间平分为两段，复杂度和前面讨论的归并排序一模一样。根据这一点，快速排序有一些常用的优化。比如，我们经常从数列中随
机取一个数当作是关键字（而不是每次总是取固定位置上的数），从而尽可能避免某些特殊的数据所导致的低效。更好的做法是随机取三个数并选择这三个数的中位
数作为关键字。而对三个数的随机取值反而将花费更多的时间，因此我们的这三个数可以分别取数列的头一个数、末一个数和正中间那个数。另外，当递归到了一定
深度发现当前区间里的数只有几个或十几个时，继续递归下去反而费时，不如返回插入排序后的结果。这种方法同时避免了当数字太少时递归操作出错的可能。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;下
面我们证明，快速排序算法的平均复杂度为O(nlogn)。不同的书上有不同的解释方法，这里我选用算法导论上的讲法。它更有技巧性一些，更有趣一些，需
要转几个弯才能想明白。<br>&nbsp;&nbsp;&nbsp;&nbsp;看一看快速排序的代码。正如我们提到过的那种分割方法，程序在经过若干次与关键字的比较后才进行一次交换，因此比
较的次数比交换次数更多。我们通过证明一次快速排序中元素之间的比较次数平均为O(nlogn)来说明快速排序算法的平均复杂度。证明的关键在于，我们需
要算出某两个元素在整个算法过程中进行过比较的概率。<br>&nbsp;&nbsp;&nbsp;&nbsp;我们举一个例子。假如给出了1到10这10个数，第一次选择关键字7将它们分成了
{1,2,3,4,5,6}和{8,9,10}两部分，递归左边时我们选择了3作为关键字，使得左部分又被分割为{1,2}和{4,5,6}。我们看到，
数字7与其它所有数都比较过一次，这样才能实现分割操作。同样地，1到6这6个数都需要与3进行一次比较（除了它本身之外）。然而，3和9决不可能相互比
较过，2和6也不可能进行过比较，因为第一次出现在3和9，2和6之间的关键字把它们分割开了。也就是说，两个数A(i)和A(j)比较过，当且仅当第一
个满足A(i)&lt;=x&lt;=A(j)的关键字x恰好就是A(i)或A(j)
（假设A(i)比A(j)小）。我们称排序后第i小的数为Z(i)，假设i&lt;j，那么第一次出现在Z(i)和Z(j)之间的关键字恰好就是Z(i)
或Z(j)的概率为2/(j-i+1)，这是因为当Z(i)和Z(j)之间还不曾有过关键字时，Z(i)和Z(j)处于同一个待分割的区间，不管这个区间
有多大，不管递归到哪里了，关键字的选择总是随机的。我们得到，Z(i)和Z(j)在一次快速排序中曾经比较过的概率为2/(j-i+1)。<br>&nbsp;&nbsp;&nbsp;&nbsp;现
在有四个数，2,3,5,7。排序时，相邻的两个数肯定都被比较过，2和5、3和7都有2/3的概率被比较过，2和7之间被比较过有2/4的可能。也就是
说，如果对这四个数做12次快速排序，那么2和3、3和5、5和7之间一共比较了12*3=36次，2和5、3和7之间总共比较了8*2=16次，2和7
之间平均比较了6次。那么，12次排序中总的比较次数期望值为36+16+6=58。我们可以计算出单次的快速排序平均比较了多少次：58/12=29
/6。其实，它就等于6项概率之和，1+1+1+2/3+2/3+2/4=29/6。这其实是与期望值相关的一个公式。<br>&nbsp;&nbsp;&nbsp;&nbsp;同样地，如果有n
个数，那么快速排序平均需要的比较次数可以写成下面的式子。令k=j-i，我们能够最终得到比较次数的期望值为O(nlogn)。<br>&nbsp;&nbsp; <img alt="" src="http://www.matrix67.com/blogimage/200704061.gif" border="0"><br>&nbsp;&nbsp;&nbsp;&nbsp;这
里用到了一个知识：1+1/2+1/3+&#8230;+1/n与log n增长速度相同，即&#931;(1/n)=&#920;(log n)。它的证明放在本文的最后。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;在
三种O(nlogn)的排序算法中，快速排序的理论复杂度最不理想，除了它以外今天说的另外两种算法都是以最坏情况O(nlogn)的复杂度进行排序。但
实践上看快速排序效率最高（不然为啥叫快速排序呢），原因在于快速排序的代码比其它同复杂度的算法更简洁，常数时间更小。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;快速排
序也有一个有趣的副产品：快速选择给出的一些数中第k小的数。一种简单的方法是使用上述任一种O(nlogn)的算法对这些数进行排序并返回排序后数组的
第k个元素。快速选择(Quick
Select)算法可以在平均O(n)的时间完成这一操作。它的最坏情况同快速排序一样，也是O(n^2)。在每一次分割后，我们都可以知道比关键字小的
数有多少个，从而确定了关键字在所有数中是第几小的。我们假设关键字是第m小。如果k=m，那么我们就找到了答案——第k小元素即该关键字。否则，我们递
归地计算左边或者右边：当k&lt;m时，我们递归地寻找左边的元素中第k小的；当k&gt;m时，我们递归地寻找右边的元素中第k-m小的数。由于我们
不考虑所有的数的顺序，只需要递归其中的一边，因此复杂度大大降低。复杂度平均线性，我们不再具体证了。<br>&nbsp;&nbsp;&nbsp;&nbsp;还有一种算法可以在最坏O(n)
的时间里找出第k小元素。那是我见过的所有算法中最没有实用价值的算法。那个O(n)只有理论价值。<br><br>============================
华丽的分割线============================<br><br>&nbsp;&nbsp;&nbsp;&nbsp;我们前面证明过，仅仅依靠交换相邻元素的操作，复杂度只
能达到O(n^2)。于是，人们尝试交换距离更远的元素。当人们发现O(nlogn)的排序算法似乎已经是极限的时候，又是什么制约了复杂度的下界呢？我
们将要讨论的是更底层的东西。我们仍然假设所有的数都不相等。<br>&nbsp;&nbsp;&nbsp;&nbsp;我们总是不断在数与数之间进行比较。你可以试试，只用4次比较绝对不可能给
4个数排出顺序。每多进行一次比较我们就又多知道了一个大小关系，从4次比较中一共可以获知4个大小关系。4个大小关系共有2^4=16种组合方式，而4
个数的顺序一共有4!=24种。也就是说，4次比较可能出现的结果数目不足以区分24种可能的顺序。更一般地，给你n个数叫你排序，可能的答案共有n!
个，k次比较只能区分2^k种可能，于是只有2^k&gt;=n!时才有可能排出顺序。等号两边取对数，于是，给n个数排序至少需要log2(n!)次。
注意，我们并没有说明一定能通过log2(n!)次比较排出顺序。虽然2^5=32超过了4!，但这不足以说明5次比较一定足够。如何用5次比较确定4个
数的大小关系还需要进一步研究。第一次例外发生在n=12的时候，虽然2^29&gt;12!，但现已证明给12个数排序最少需要30次比较。我们可以证
明log(n!)的增长速度与nlogn相同，即log(n!)=&#920;(nlogn)。这是排序所需要的最少的比较次数，它给出了排序复杂度的一个下界。
log(n!)=&#920;(nlogn)的证明也附在本文最后。<br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="http://www.matrix67.com/blog/article.asp?id=191" target="_blank"><strong> <font color="#618898">这篇日志</font></strong> </a>的第
三题中证明log2(N)是最优时用到了几乎相同的方法。那种&#8220;用天平称出重量不同的那个球至少要称几次&#8221;一类题目也可以用这种方法来解决。事实上，这里
有一整套的理论，它叫做信息论。信息论是由香农(Shannon)提出的。他用对数来表示信息量，用熵来表示可能的情况的随机性，通过运算可以知道你目前
得到的信息能够怎样影响最终结果的确定。如果我们的信息量是以2为底的，那信息论就变成信息学了。从根本上说，计算机的一切信息就是以2为底的信息量
(bits=<u>bi</u>nary digi<u>ts</u>)，因此我们常说香农是数字通信之父。信息论和热力学关系密切，比如熵的概念是直接
从热力学的熵定义引申过来的。和这个有关的东西已经严重偏题了，这里不说了，有兴趣可以去看《信息论与编码理论》。我对这个也很有兴趣，半懂不懂的，很想
了解更多的东西，有兴趣的同志不妨加入讨论。物理学真的很神奇，利用物理学可以解决很多纯数学问题，我有时间的话可以举一些例子。我他妈的为啥要选文科
呢。<br>&nbsp;&nbsp;&nbsp;&nbsp;后面将介绍的三种排序是线性时间复杂度，因为，它们排序时根本不是通过互相比较来确定大小关系的。<br><br><br>附
1：&#931;(1/n)=&#920;(log n)的证明<br>&nbsp;&nbsp;&nbsp;&nbsp;首先我们证明，&#931;(1/n)=O(log
n)。在式子1+1/2+1/3+1/4+1/5+&#8230;中，我们把1/3变成1/2，使得两个1/2加起来凑成一个1；再把1/5,1/6和1/7全部变成
1/4，这样四个1/4加起来又是一个1。我们把所有1/2^k的后面2^k-1项全部扩大为1/2^k，使得这2^k个分式加起来是一个1。现
在，1+1/2+&#8230;+1/n里面产生了几个1呢？我们只需要看小于n的数有多少个2的幂即可。显然，经过数的扩大后原式各项总和为log
n。O(logn)是&#931;(1/n)的复杂度上界。<br>&nbsp;&nbsp;&nbsp;&nbsp;然后我们证明，&#931;(1/n)=&#937;(log
n)。在式子1+1/2+1/3+1/4+1/5+&#8230;中，我们把1/3变成1/4，使得两个1/4加起来凑成一个1/2；再把1/5,1/6和1/7全部
变成1/8，这样四个1/8加起来又是一个1/2。我们把所有1/2^k的前面2^k-1项全部缩小为1/2^k，使得这2^k个分式加起来是一个1
/2。现在，1+1/2+&#8230;+1/n里面产生了几个1/2呢？我们只需要看小于n的数有多少个2的幂即可。显然，经过数的缩小后原式各项总和为1
/2*logn。&#937;(logn)是&#931;(1/n)的复杂度下界。<br><br><br>附2：log(n!)=&#920;(nlogn)的证明<br>&nbsp;&nbsp;&nbsp;&nbsp;首
先我们证明，log(n!)=O(nlogn)。显然n!&lt;n^n，两边取对数我们得到log(n!)&lt;log(n^n)，而
log(n^n)就等于nlogn。因此，O(nlogn)是log(n!)的复杂度上界。<br>&nbsp;&nbsp;&nbsp;&nbsp;然后我们证
明，log(n!)=&#937;(nlogn)。n!=n(n-1)(n-2)(n-3)&#8230;.1，把前面一半的因子全部缩小到n/2，后面一半因子全部舍去，显然
有n!&gt;(n/2)^(n/2)。两边取对数，log(n!)&gt;(n/2)log(n/2)，后者即&#937;(nlogn)。因
此，&#937;(nlogn)是log(n!)的复杂度下界。<br><br>今天写到这里了，大家帮忙校对哦<br>Matrix67原创<br>转贴请注明出
处<br><a  href="http://www.matrix67.com/blog/archives/166" title="Permanent link to
从零开始学算法：十种排序算法介绍（上）" rel="bookmark"><u><font color="#800080">从零开始学算法：十种排序算法介绍（下）</font></u></a><br>那么，有什么方法可以不用比较就能排
出顺序呢？借助Hash表的思想，多数人都能想出这样一种排序算法来。<br>&nbsp;&nbsp;&nbsp;&nbsp;我们假设给出的数字都在一定范围中，那么我们就可以开一个范围相同
的数组，记录这个数字是否出现过。由于数字有可能有重复，因此Hash表的概念需要扩展，我们需要把数组类型改成整型，用来表示每个数出现的次数。<br>&nbsp;&nbsp;&nbsp;&nbsp;看
这样一个例子，假如我们要对数列3 1 4 1 5 9 2 6 5 3 5
9进行排序。由于给定数字每一个都小于10，因此我们开一个0到9的整型数组T[i]，记录每一个数出现了几次。读到一个数字x，就把对应的T[x]加
一。<br><br>&nbsp;&nbsp;A[]= 3, 1, 4, 1, 5, 9, 2, 6,
5, 3, 5, 9<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +---+---+---+---+---+---+---+---+---+---+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;数
字 i： | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+---+---+---+---+---+---+---+---+---+<br>出现次数T[i]： | 0 | 2 | 1 | 2 |
1 | 3 | 1 | 0 | 0 | 2 |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+---+---+---+---+---+---+---+---+---+<br><br>&nbsp;&nbsp;&nbsp;&nbsp;最后，我们用一个指针从前
往后扫描一遍，按照次序输出0到9，每个数出现了几次就输出几个。假如给定的数是n个大小不超过m的自然数，显然这个算法的复杂度是O(m+n)的。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;我
曾经以为，这就是线性时间排序了。后来我发现我错了。再后来，我发现我曾犯的错误是一个普遍的错误。很多人都以为上面的这个算法就是传说中的计数排序。问
题出在哪里了？为什么它不是线性时间的排序算法？原因是，这个算法根本不是排序算法，它根本没有对原数据进行排序。<br><br><br><strong> 问
题一：为什么说上述算法没有对数据进行排序？</strong> <br>STOP! You
should think for a while.<br><br>&nbsp;&nbsp;&nbsp;&nbsp;我们班有很多MM。和身高相差太远的MM在一起肯定很别扭，
接个吻都要弯腰才行（<a href="http://fayecatshome.spaces.live.com/" target="_blank"><strong> <font color="#618898">小猫</font></strong> </a>矮死了）。为此，我希望给我们班的MM的身高排序。我们班MM的身高，
再离谱也没有超过2米的，这很适合用我们刚才的算法。我们在黑板上画一个100到200的数组，MM依次自曝身高，我负责画&#8220;正&#8221;字统计人数。统计出来
了，从小到大依次为141, 143, 143, 147, 152, 153,
&#8230;。这算哪门子排序？就一排数字对我有什么用，我要知道的是哪个MM有多高。我们仅仅把元素的属性值从小到大列了出来，但我们没有对元素本身进行排序。也
就是说，我们需要知道输出结果的每个数值对应原数据的哪一个元素。下文提到的&#8220;排序算法的稳定性&#8221;也和属性值与实际元素的区别有关。<br><br><br><strong> 问
题二：怎样将线性时间排序后的输出结果还原为原数据中的元素？</strong> <br>STOP!
You should think for a while.<br><br>&nbsp;&nbsp;&nbsp;&nbsp;同样借助Hash表的思想，我们立即想到了类似于
开散列的方法。我们用链表把属性值相同的元素串起来，挂在对应的T[i]上。每次读到一个数，在增加T[i]的同时我们把这个元素放进T[i]延伸出去的
链表里。这样，输出结果时我们可以方便地获得原数据中的所有属性值为i的元素。<br><br>&nbsp;&nbsp;A[]= 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 9<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+---+---+---+---+---+---+---+---+---+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;数字 i： | 0 | 1 | 2 | 3
| 4 | 5 | 6 | 7 | 8 | 9 |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+---+---+---+---+---+---+---+---+---+<br>出现次数T[i]： | 0 | 2 | 1 | 2 |
1 | 3 | 1 | 0 | 0 | 2 |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+o--+-o-+-o-+-o-+-o-+--o+---+---+-o-+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;
|&nbsp;&nbsp; |&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +--+&nbsp;&nbsp;+-+&nbsp;&nbsp; |&nbsp;&nbsp; |&nbsp;&nbsp;
+-+&nbsp;&nbsp;+---+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp; A[1]&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;
A[6]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A[2]&nbsp;&nbsp;A[7]&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;A[3]&nbsp;&nbsp;A[5]&nbsp;&nbsp; A[8]&nbsp;&nbsp;&nbsp;&nbsp;|<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;A[12]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
A[4]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;A[10]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;A[9]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;A[11]<br><br>&nbsp;&nbsp;&nbsp;&nbsp;形
象地说，我们在地上摆10个桶，每个桶编一个号，然后把数据分门别类放在自己所属的桶里。这种排序算法叫做桶式排序(Bucket
Sort)。本文最后你将看到桶式排序的另一个用途。<br>&nbsp;&nbsp;&nbsp;&nbsp;链表写起来比较麻烦，一般我们不使用它。我们有更简单的方法。<br><br><br><strong> 问
题三：同样是输出元素本身，你能想出不用链表的其它算法么？</strong> <br>STOP!
You should think for a while.<br><br>&nbsp;&nbsp;A[]= 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 9<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+---+---+---+---+---+---+---+---+---+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;数字 i： | 0 | 1 | 2 | 3
| 4 | 5 | 6 | 7 | 8 | 9 |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+---+---+---+---+---+---+---+---+---+<br>出现次数T[i]： | 0 | 2 | 1 | 2 |
1 | 3 | 1 | 0 | 0 | 2 |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+---+---+---+---+---+---+---+---+---+<br>修改后的T[i]： | 0 | 2 | 3 | 5 |
6 | 9 | 10| 10| 10| 12|<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+---+---+---+---+---+---+---+---+---+<br><br>&nbsp;&nbsp;&nbsp;&nbsp;所有数都读入后，我们修改
T[i]数组的值，使得T[i]表示数字i可能的排名的最大值。比如，1最差排名第二，3最远可以排到第五。T数组的最后一个数应该等于输入数据的数字个
数。修改T数组的操作可以用一次线性的扫描累加完成。<br>&nbsp;&nbsp;&nbsp;&nbsp;我们还需要准备一个输出数组。然后，我们从后往前扫描A数组，依照T数组的指示依次
把原数据的元素直接放到输出数组中，同时T[i]的值减一。之所以从后往前扫描A数组，是因为这样输出结果才是稳定的。我们说一个排序算法是稳定的
(Stable)，当算法满足这样的性质：属性值相同的元素，排序后前后位置不变，本来在前面的现在仍然在前面。不要觉得排序算法是否具有稳定性似乎关系
不大，排序的稳定性在下文的某个问题中将变得非常重要。你可以倒回去看看前面说的七种排序算法哪些是稳定的。<br>&nbsp;&nbsp;&nbsp;&nbsp;例子中，A数组最后一个数9
所对应的T[9]=12，我们直接把9放在待输出序列中的第12个位置，然后T[9]变成11（这样下一次再出现9时就应该放在第11位）。<br><br>A[]= 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 9 &lt;--<br>T[i]=
0, 2, 3, 5, 6, 9, 10, 10, 10, 11<br>Ans
= _ _ _ _ _ _ _ _ _ _ _ 9<br><br>&nbsp;&nbsp;&nbsp;&nbsp;接下来的几步如下：<br><br>A[]= 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5 &lt;--<br>T[i]=
0, 2, 3, 5, 6, 8, 10, 10, 10, 11<br>Ans
= _ _ _ _ _ _ _ _ 5 _ _ 9<br><br>A[]= 3, 1, 4, 1, 5, 9, 2, 6, 5, 3
&lt;--<br>T[i]= 0, 2, 3, 4, 6, 8, 10,
10, 10, 11<br>Ans = _ _ _ _ 3 _ _ _ 5 _ _ 9<br><br>A[]= 3, 1, 4, 1, 5,
9, 2, 6, 5 &lt;--<br>T[i]= 0, 2, 3, 4, 6, 7,
10, 10, 10, 11<br>Ans = _ _ _ _ 3 _ _ 5 5 _ _ 9<br><br>&nbsp;&nbsp;&nbsp;&nbsp;这种算法叫
做计数排序(Counting Sort)。正确性和复杂度都是显然的。<br><br><br><strong> 问题四：给定数的数据范围大了该怎么
办？</strong> <br>STOP! You should think for a
while.<br><br>&nbsp;&nbsp;&nbsp;&nbsp;前面的算法只有在数据的范围不大时才可行，如果给定的数在长整范围内的话，这个算法是不可行的，因为
你开不下这么大的数组。Radix排序(Radix Sort)解决了这个难题。<br>&nbsp;&nbsp;&nbsp;&nbsp;昨天我没事翻了一下初中（9班）时的同学录，回忆了一下
过去。我把比较感兴趣的MM的生日列在下面（绝对真实）。如果列表中的哪个MM有幸看到了这篇日志（几乎不可能），左边的Support栏有我的电子联系
方式，我想知道你们怎么样了。排名不分先后。<br>
</strong>
<ul>
    <li><strong style="color: #0000ff;">19880818<br>
    </strong></li>
    <li><strong style="color: #0000ff;">19880816<br>
    </strong></li>
    <li><strong style="color: #0000ff;">19890426<br>
    </strong></li>
    <li><strong style="color: #0000ff;">19880405<br>
    </strong></li>
    <li><strong style="color: #0000ff;">19890125<br>
    </strong></li>
    <li><strong style="color: #0000ff;">19881004<br>
    </strong></li>
    <li><strong style="color: #0000ff;">19881209<br>
    </strong></li>
    <li><strong style="color: #0000ff;">19890126<br>
    </strong></li>
    <li><strong style="color: #0000ff;">19890228 </strong></li>
</ul>
<p><strong style="color: #0000ff;"><br>&nbsp;&nbsp;&nbsp;&nbsp;这就是我的数据了。现在，我要给这些数排序。假如我的电脑只能开出0..99的数组，那计数排序算法最多对两位数进行排序。我就把
每个八位数两位两位地分成四段（图1），分别进行四次计数排序。地球人都知道月份相同时应该看哪一日，因此我们看月份的大小时应该事先保证日已经有序。换
句话说，我们先对&#8220;最不重要&#8221;的部分进行排序。我们先对所有数的最后两位进行一次计数排序（图2）。注意观察1月26号的MM和4月26号的MM，本次排
序中它们的属性值相同，由于计数排序是稳定的，因此4月份那个排完后依然在1月份那个的前头。接下来我们对百位和千位进行排序（图3）。你可以看到两个
26日的MM在这一次排序中分出了大小，而月份相同的MM依然保持日数有序（因为计数排序是稳定的）。最后我们对年份排序（图4），完成整个算法。大家都
是跨世纪的好儿童，因此没有图5了。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <img alt="" src="http://www.matrix67.com/blogimage/200704131.gif" border="0"><br><br>&nbsp;&nbsp;&nbsp;&nbsp;这
种算法显然是正确的。它的复杂度一般写成O(d*(n+m))，其中n表示n个数，m是我开的数组大小（本例中m=100），d是一个常数因子（本例中
d=4）。我们认为它也是线性的。<br><br><br><strong> 问题五：这样的排序方法还有什么致命的缺陷？</strong> <br>STOP! You should think for a while.<br><br>&nbsp;&nbsp;&nbsp;&nbsp;即
使数据有30位，我们也可以用d=5或6的Radix算法进行排序。但，要是给定的数据有无穷多位怎么办？有人说，这可能么。这是可能的，比如给定的数据
是小数（更准确地说，实数）。基于比较的排序可以区分355/113和&#960;哪
个大，但你不知道Radix排序需要精确到哪一位。这下惨了，实数的出现把貌似高科技的线性时间排序打回了农业时代。这时，桶排序再度出山，挽救了线性时
间排序悲惨的命运。<br><br><br><strong> 问题六：如何对实数进行线性时间排序？</strong> <br>STOP! You should think for a while.<br><br>&nbsp;&nbsp;&nbsp;&nbsp;我
们把问题简化一下，给出的所有数都是0到1之间的小数。如果不是，也可以把所有数同时除以一个大整数从而转化为这种形式。我们依然设立若干个桶，比如，以
小数点后面一位数为依据对所有数进行划分。我们仍然用链表把同一类的数串在一起，不同的是，每一个链表都是有序的。也就是说，每一次读到一个新的数都要进
行一次插入排序。看我们的例子：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;A[]=
0.12345, 0.111, 0.618, 0.9, 0.99999<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+---+---+---+---+---+---+---+---+---+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;十分位： | 0 | 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+---+-o-+---+---+---+---+-o-+---+---+-o-+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A[2]=0.111&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;A[3]=0.618&nbsp;&nbsp; A[4]=0.9<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A[1]=0.12345&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A[5]=0.99999<br><br>&nbsp;&nbsp;&nbsp;&nbsp;假如再下一个读入的数是
0.122222，这个数需要插入到十分位为1的那个链表里适当的位置。我们需要遍历该链表直到找到第一个比0.122222大的数，在例子中则应该插入
到链表中A[2]和A[1]之间。最后，我们按顺序遍历所有链表，依次输出每个链表中的每个数。<br>&nbsp;&nbsp;&nbsp;&nbsp;这个算法显然是正确的，但复杂度显然不是
线性。事实上，这种算法最坏情况下是O(n^2)的，因为当所有数的十分位都相同时算法就是一个插入排序。和原来一样，我们下面要计算算法的平均时间复杂
度，我们希望这种算法的平均复杂度是线性的。<br>&nbsp;&nbsp;&nbsp;&nbsp;这次算平均复杂度我们用最笨的办法。我们将算出所有可能出现的情况的总时间复杂度，除以总的
情况数，得到平均的复杂度是多少。<br>&nbsp;&nbsp;&nbsp;&nbsp;每个数都可能属于10个桶中的一个，n个数总的情况有10^n种。这个值是我们庞大的算式的分母部分。
如果一个桶里有K个元素，那么只与这个桶有关的操作有O(K^2)次，它就是一次插入排序的操作次数。下面计算，在10^n种情况中，K0=1有多少种情
况。K0=1表示，n个数中只有一个数在0号桶，其余n-1个数的十分位就只能在1到9中选择。那么K0=1的情况有C(n,1)*9^(n-1)，而每
个K0=1的情况在0号桶中将产生1^2的复杂度。类似地，Ki=p的情况数为C(n,p)*9^(n-p)，复杂度总计为C(n,p)*9^(n-
p)*p^2。枚举所有K的下标和p值，累加起来，这个算式大家应该能写出来了，但是这个&#8230;&#8230;怎么算啊。别怕，我们是搞计算机的，拿出点和MO不一样的东
西来。于是，Mathematica 5.0隆重登场，我做数学作业全靠它。它将帮我们化简这个复杂的式子。<br><img alt="" src="http://www.matrix67.com/blogimage/200704132.gif" border="0"><br><br>&nbsp;&nbsp;&nbsp;&nbsp;我
们遗憾地发现，虽然常数因子很小（只有0.1），但算法的平均复杂度仍然是平方的。等一下，1/10的那个10是我们桶的个数吗？那么我们为什么不把桶的
个数弄大点？我们干脆用m来表示桶的个数，重新计算一次：<br><img alt="" src="http://www.matrix67.com/blogimage/200704133.gif" border="0"><br><br>&nbsp;&nbsp;&nbsp;&nbsp;化
简出来，操作次数为O(n+n^2/m)。发现了么，如果m=&#920;(n)的话，平均复杂度就变成了O(n)。也就是说，当桶的个数等于输入数据的个数时，算
法是平均线性的。<br>&nbsp;&nbsp;&nbsp;&nbsp;我们将在Hash表开散列的介绍中重新提到这个结论。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;且慢，还有一个问题。10个桶以十分位的
数字归类，那么n个桶用什么方法来分类呢？注意，分类的方法需要满足，一，一个数分到每个桶里的概率相同（这样才有我们上面的结论）；二，所有桶里容纳元
素的范围必须是连续的。根据这两个条件，我们有办法把所有数恰好分为n类。我们的输入数据不是都在0到1之间么？只需要看这些数乘以n的整数部分是多少就
行了，读到一个数后乘以n取整得几就插入到几号桶里。这本质上相当于把区间[0,1)平均分成n份。<br><br><br><strong> 问题七：有
没有复杂度低于线性的排序算法</strong> <br>STOP! You should
think for a while.<br><br>&nbsp;&nbsp;&nbsp;&nbsp;我们从O(n^2)走向O(nlogn)，又从O(nlogn)走向线性，
每一次我们都讨论了复杂度下限的问题，根据讨论的结果提出了更优的算法。这次总算不行了，不可能有比线性还快的算法了，因为——你读入、输出数据至少就需
要线性的时间。排序算法之旅在线性时间复杂度这一站终止了，所有十种排序算法到这里介绍完毕了。<br><br><br><br>&nbsp;&nbsp;&nbsp;&nbsp;文章有越写越长
的趋势了，我检查起来也越来越累了。我又看了三遍，应该没问题了。群众的眼睛是雪亮的，恳请大家帮我找错。<br></strong></p>
<strong style="color: #0000ff;">
</strong><br> <img src ="http://www.cppblog.com/yuqilin1228/aggbug/110617.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yuqilin1228/" target="_blank">LynnRaymond</a> 2010-03-26 21:54 <a href="http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110617.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载】位运算简介及实用技巧</title><link>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110616.html</link><dc:creator>LynnRaymond</dc:creator><author>LynnRaymond</author><pubDate>Fri, 26 Mar 2010 13:48:00 GMT</pubDate><guid>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110616.html</guid><wfw:comment>http://www.cppblog.com/yuqilin1228/comments/110616.html</wfw:comment><comments>http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110616.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yuqilin1228/comments/commentRss/110616.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yuqilin1228/services/trackbacks/110616.html</trackback:ping><description><![CDATA[<a title="Permanent link to 位运算简介及实用技巧（一）：基础篇" href="http://www.matrix67.com/blog/archives/263" rel="bookmark">位
运算简介及实用技巧（一）：基础篇</a>&nbsp;&nbsp; FROM:Matrix67大牛
<br>去年年底写的关于<a href="http://www.matrix67.com/blog/article.asp?id=153" target="_blank">位运算</a>的日志是这个Blog里少数大受欢迎的文章之一，很多人都希望我能不断完善那篇文章。后来我看到了不少其
它的资料，学习到了更多关于位运算的知识，有了重新整理位运算技巧的想法。从今天起我就开始写这一系列位运算讲解文章，与其说是原来那篇文章的
follow-up，不如说是一个remake。当然首先我还是从最基础的东西说起。<br><br>什么是位运算？<br>&nbsp;&nbsp;&nbsp;&nbsp;程序中的所有数在计
算机内存中都是以二进制的形式储存的。位运算说穿了，就是直接对整数在内存中的二进制位进行操作。比如，and运算本来是一个逻辑运算符，但整数与整数之
间也可以进行and运算。举个例子，6的二进制是110，11的二进制是1011，那么6 and
11的结果就是2，它是二进制对应位进行逻辑运算的结果（0表示False，1表示True，空位都当0处理）：<br>&nbsp;&nbsp;&nbsp;&nbsp; 110<br>AND
1011<br>----------<br>&nbsp;&nbsp;&nbsp;&nbsp;0010&nbsp;&nbsp;--&gt;&nbsp;&nbsp;2<br>&nbsp;&nbsp;&nbsp;&nbsp;由于位运算直接对内存数据进行操作，不需要转成
十进制，因此处理速度非常快。当然有人会说，这个快了有什么用，计算6 and
11没有什么实际意义啊。这一系列的文章就将告诉你，位运算到底可以干什么，有些什么经典应用，以及如何用位运算优化你的程序。<br><br><br>Pascal
和C中的位运算符号<br>&nbsp;&nbsp;&nbsp;&nbsp;下面的a和b都是整数类型，则：<br>C语言&nbsp;&nbsp;|&nbsp;&nbsp;Pascal语言<br>-------+-------------<br>a
&amp; b&nbsp;&nbsp;|&nbsp;&nbsp;a and b<br>a | b&nbsp;&nbsp;|&nbsp;&nbsp;a or b<br>a ^ b&nbsp;&nbsp;|&nbsp;&nbsp;a xor b<br>&nbsp;&nbsp;~a&nbsp;&nbsp;
|&nbsp;&nbsp; not a<br>a &lt;&lt; b |&nbsp;&nbsp;a shl b<br>a &gt;&gt; b |&nbsp;&nbsp;a shr b<br>&nbsp;&nbsp;&nbsp;&nbsp;注
意C中的逻辑运算和位运算符号是不同的。520|1314=1834，但520||1314=1，因为逻辑运算时520和1314都相当于True。同样
的，!a和~a也是有区别的。<br><br><br>各种位运算的使用<br>&nbsp;&nbsp;&nbsp;&nbsp;=== 1. and运算 ===<br>&nbsp;&nbsp;&nbsp;&nbsp;and运算通
常用于二进制取位操作，例如一个数 and
1的结果就是取二进制的最末位。这可以用来判断一个整数的奇偶，二进制的最末位为0表示该数为偶数，最末位为1表示该数为奇数.<br><br>&nbsp;&nbsp;&nbsp;&nbsp;===
2. or运算 ===<br>&nbsp;&nbsp;&nbsp;&nbsp;or运算通常用于二进制特定位上的无条件赋值，例如一个数or
1的结果就是把二进制最末位强行变成1。如果需要把二进制最末位变成0，对这个数or
1之后再减一就可以了，其实际意义就是把这个数强行变成最接近的偶数。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;=== 3. xor运算 ===<br>&nbsp;&nbsp;&nbsp;&nbsp;xor
运算通常用于对二进制的特定一位进行取反操作，因为异或可以这样定义：0和1异或0都不变，异或1则取反。<br>&nbsp;&nbsp;&nbsp;&nbsp;xor运算的逆运算是它本身，
也就是说两次异或同一个数最后结果不变，即(a xor b) xor b =
a。xor运算可以用于简单的加密，比如我想对我MM说1314520，但怕别人知道，于是双方约定拿我的生日19880516作为密钥。1314520
xor 19880516 = 20665500，我就把20665500告诉MM。MM再次计算20665500 xor
19880516的值，得到1314520，于是她就明白了我的企图。<br>&nbsp;&nbsp;&nbsp;&nbsp;下面我们看另外一个东西。定义两个符号#和@（我怎么找不到那个圈
里有个叉的字符），这两个符号互为逆运算，也就是说(x # y) @ y = x。现在依次执行下面三条命令，结果是什么？<br><code>x
&lt;- x # y<br>y &lt;- x @ y<br>x &lt;- x @ y</code><br>&nbsp;&nbsp;&nbsp;&nbsp;执行了第一句后x变成了x
# y。那么第二句实质就是y &lt;- x # y @ y，由于#和@互为逆运算，那么此时的y变成了原来的x。第三句中x实际上被赋值为(x #
y) @ x，如果#运算具有交换律，那么赋值后x就变成最初的y了。这三句话的结果是，x和y的位置互换了。<br>&nbsp;&nbsp;&nbsp;&nbsp;加法和减法互为逆运算，
并且加法满足交换律。把#换成+，把@换成-，我们可以写出一个不需要临时变量的swap过程(Pascal)。<br><code>procedure
swap(var a,b:longint);<br>begin<br>&nbsp;&nbsp; a:=a + b;<br>&nbsp;&nbsp; b:=a - b;<br>&nbsp;&nbsp;
a:=a - b;<br>end;</code><br>&nbsp;&nbsp;&nbsp;&nbsp;好了，刚才不是说xor的逆运算是它本身吗？于是我们就有了一个看起来非常诡异的
swap过程：<br><code>procedure swap(var a,b:longint);<br>begin<br>&nbsp;&nbsp; a:=a
xor b;<br>&nbsp;&nbsp; b:=a xor b;<br>&nbsp;&nbsp; a:=a xor b;<br>end;</code><br><br>&nbsp;&nbsp;&nbsp;&nbsp;===
4. not运算 ===<br>&nbsp;&nbsp;&nbsp;&nbsp;not运算的定义是把内存中的0和1全部取反。使用not运算时要格外小心，你需要注意整数类型有没有符号。
如果not的对象是无符号整数（不能表示负数），那么得到的值就是它与该类型上界的差，因为无符号类型的数是用$0000到$FFFF依次表示的。下面的
两个程序（仅语言不同）均返回65435。<br><code>var<br>&nbsp;&nbsp; a:word;<br>begin<br>&nbsp;&nbsp; a:=100;<br>&nbsp;&nbsp;
a:=not a;<br>&nbsp;&nbsp; writeln(a);<br>end.</code><br><code>#include
&lt;stdio.h&gt;<br>int main()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;unsigned short a=100;<br>&nbsp;&nbsp;&nbsp;&nbsp;a
= ~a;<br>&nbsp;&nbsp;&nbsp;&nbsp;printf( "%d\n", a );&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br>}</code><br>&nbsp;&nbsp;&nbsp;&nbsp;如
果not的对象是有符号的整数，情况就不一样了，稍后我们会在&#8220;整数类型的储存&#8221;小节中提到。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;=== 5. shl运算 ===<br>&nbsp;&nbsp;&nbsp;&nbsp;a
shl
b就表示把a转为二进制后左移b位（在后面添b个0）。例如100的二进制为1100100，而110010000转成十进制是400，那么100
shl 2 = 400。可以看出，a shl b的值实际上就是a乘以2的b次方，因为在二进制数后添一个0就相当于该数乘以2。<br>&nbsp;&nbsp;&nbsp;&nbsp;通常
认为a shl 1比a * 2更快，因为前者是更底层一些的操作。因此程序中乘以2的操作请尽量用左移一位来代替。<br>&nbsp;&nbsp;&nbsp;&nbsp;定义一些常量可能会
用到shl运算。你可以方便地用1 shl 16 -
1来表示65535。很多算法和数据结构要求数据规模必须是2的幂，此时可以用shl来定义Max_N等常量。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;=== 6.
shr运算 ===<br>&nbsp;&nbsp;&nbsp;&nbsp;和shl相似，a shr
b表示二进制右移b位（去掉末b位），相当于a除以2的b次方（取整）。我们也经常用shr 1来代替div
2，比如二分查找、堆的插入操作等等。想办法用shr代替除法运算可以使程序效率大大提高。最大公约数的二进制算法用除以2操作来代替慢得出奇的mod运
算，效率可以提高60%。<br><br><br>位运算的简单应用<br>&nbsp;&nbsp;&nbsp;&nbsp;有时我们的程序需要一个规模不大的Hash表来记录状态。比如，做数
独时我们需要27个Hash表来统计每一行、每一列和每一个小九宫格里已经有哪些数了。此时，我们可以用27个小于2^9的整数进行记录。例如，一个只填
了2和5的小九宫格就用数字18表示（二进制为000010010），而某一行的状态为511则表示这一行已经填满。需要改变状态时我们不需要把这个数转
成二进制修改后再转回去，而是直接进行位操作。在搜索时，把状态表示成整数可以更好地进行判重等操作。<a href="http://www.vijos.cn/Problem_Show.asp?id=1197" target="_blank">这道题</a>是在搜索中使用位运算加速的经典例子。以后我们会看到更多的例子。<br>&nbsp;&nbsp;&nbsp;&nbsp;下面列举了一些常见的
二进制位的变换操作。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;功能&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 示例&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;位运算<br>----------------------+---------------------------+--------------------<br>去
掉最后一位&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| (101101-&gt;10110)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | x shr 1<br>在最后加一个
0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | (101101-&gt;1011010)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | x shl 1<br>在最后加一个1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |
(101101-&gt;1011011)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | x shl 1+1<br>把最后一位变成1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |
(101100-&gt;101101)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x or 1<br>把最后一位变成0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |
(101101-&gt;101100)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x or 1-1<br>最后一位取反&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|
(101101-&gt;101100)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x xor 1<br>把右数第k位变成1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|
(101001-&gt;101101,k=3)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x or (1 shl (k-1))<br>把右数第k位变成0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|
(101101-&gt;101001,k=3)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x and not (1 shl (k-1))<br>右数第k位取
反&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | (101001-&gt;101101,k=3)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x xor (1 shl (k-1))<br>取末三
位&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| (1101101-&gt;101)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x and 7<br>取末k
位&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | (1101101-&gt;1101,k=5)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | x and (1 shl k-1)<br>取右
数第k位&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | (1101101-&gt;1,k=4)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x shr (k-1) and 1<br>把末k
位变成1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| (101001-&gt;101111,k=4)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x or (1 shl k-1)<br>末k位取
反&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | (101001-&gt;100110,k=4)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x xor (1 shl k-1)<br>把右边连
续的1变成0&nbsp;&nbsp;&nbsp;&nbsp;| (100101111-&gt;100100000)&nbsp;&nbsp;&nbsp;&nbsp;| x and (x+1)<br>把右起第一个0变成
1&nbsp;&nbsp;&nbsp;&nbsp;| (100101111-&gt;100111111)&nbsp;&nbsp;&nbsp;&nbsp;| x or (x+1)<br>把右边连续的0变成1&nbsp;&nbsp;&nbsp;&nbsp;|
(11011000-&gt;11011111)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| x or (x-1)<br>取右边连续的1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |
(100101111-&gt;1111)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | (x xor (x+1)) shr 1<br>去掉右起第一个1的左边 |
(100101000-&gt;1000)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | x and (x xor (x-1))<br><br>&nbsp;&nbsp;&nbsp;&nbsp;最后这一个在树状数组
中会用到。<br><br><br>Pascal和C中的16进制表示<br>&nbsp;&nbsp;&nbsp;&nbsp;Pascal中需要在16进制数前加$符号表示，C中需要在前面加
0x来表示。这个以后我们会经常用到。<br><br>整数类型的储存<br>&nbsp;&nbsp;&nbsp;&nbsp;我们前面所说的位运算都没有涉及负数，都假设这些运算是在
unsigned/word类型（只能表示正数的整型）上进行操作。但计算机如何处理有正负符号的整数类型呢？下面两个程序都是考察16位整数的储存方式
（只是语言不同）。<br><code>var<br>&nbsp;&nbsp; a,b:integer;<br>begin<br>&nbsp;&nbsp; a:=$0000;<br>&nbsp;&nbsp;
b:=$0001;<br>&nbsp;&nbsp; write(a,' ',b,' ');<br>&nbsp;&nbsp; a:=$FFFE;<br>&nbsp;&nbsp; b:=$FFFF;<br>&nbsp;&nbsp;
write(a,' ',b,' ');<br>&nbsp;&nbsp; a:=$7FFF;<br>&nbsp;&nbsp; b:=$8000;<br>&nbsp;&nbsp; writeln(a,'
',b);<br>end.</code><br><code>#include &lt;stdio.h&gt;<br>int main()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;short
int a, b;<br>&nbsp;&nbsp;&nbsp;&nbsp;a = 0&#215;0000;<br>&nbsp;&nbsp;&nbsp;&nbsp;b = 0&#215;0001;<br>&nbsp;&nbsp;&nbsp;&nbsp;printf( "%d %d
", a, b );<br>&nbsp;&nbsp;&nbsp;&nbsp;a = 0xFFFE;<br>&nbsp;&nbsp;&nbsp;&nbsp;b = 0xFFFF;<br>&nbsp;&nbsp;&nbsp;&nbsp;printf( "%d %d
", a, b );<br>&nbsp;&nbsp;&nbsp;&nbsp;a = 0&#215;7FFF;<br>&nbsp;&nbsp;&nbsp;&nbsp;b = 0&#215;8000;<br>&nbsp;&nbsp;&nbsp;&nbsp;printf( "%d
%d\n", a, b );<br>&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br>}</code><br>&nbsp;&nbsp;&nbsp;&nbsp;两个程序的输出均为0 1 -2 -1
32767
-32768。其中前两个数是内存值最小的时候，中间两个数则是内存值最大的时候，最后输出的两个数是正数与负数的分界处。由此你可以清楚地看到计算机是
如何储存一个整数的：计算机用$0000到$7FFF依次表示0到32767的数，剩下的$8000到$FFFF依次表示-32768到-1的数。32位
有符号整数的储存方式也是类似的。稍加注意你会发现，二进制的第一位是用来表示正负号的，0表示正，1表示负。这里有一个问题：0本来既不是正数，也不是
负数，但它占用了$0000的位置，因此有符号的整数类型范围中正数个数比负数少一个。对一个有符号的数进行not运算后，最高位的变化将导致正负颠倒，
并且数的绝对值会差1。也就是说，not a实际上等于-a-1。这种整数储存方式叫做&#8220;补码&#8221;。<br><br>最后还有两句话<br>&nbsp;&nbsp;&nbsp;&nbsp;Matrix67
原创<br>&nbsp;&nbsp;&nbsp;&nbsp;转贴请注明出处<br><br><a href="http://www.matrix67.com/blog/archives/264" title="Permanent link to
位运算简介及实用技巧（二）：进阶篇(1)" rel="bookmark">位
运算简介及实用技巧（二）：进阶篇(1)</a> <br>
<p>=====&nbsp;&nbsp; 真正强的东西来了！&nbsp;&nbsp; =====<br><br>二进制中的1有奇数个还是偶数个<br>&nbsp;&nbsp;&nbsp;&nbsp;我们可以用下面的代码来计算
一个32位整数的二进制中1的个数的奇偶性，当输入数据的二进制表示里有偶数个数字1时程序输出0，有奇数个则输出1。例如，1314520的二进制
101000000111011011000中有9个1，则x=1314520时程序输出1。<br><code>var<br>&nbsp;&nbsp;
i,x,c:longint;<br>begin<br>&nbsp;&nbsp; readln(x);<br>&nbsp;&nbsp; c:=0;<br>&nbsp;&nbsp; for i:=1 to
32 do<br>&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;c:=c + x and 1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x:=x shr 1;<br>&nbsp;&nbsp;
end;<br>&nbsp;&nbsp; writeln( c and 1 );<br>end.</code><br>&nbsp;&nbsp;&nbsp;&nbsp;但这样的效率并不高，位运算的神奇之处还
没有体现出来。<br>&nbsp;&nbsp;&nbsp;&nbsp;同样是判断二进制中1的个数的奇偶性，下面这段代码就强了。你能看出这个代码的原理吗？<br><code>var<br>&nbsp;&nbsp;
x:longint;<br>begin<br>&nbsp;&nbsp; readln(x);<br>&nbsp;&nbsp; x:=x xor (x shr 1);<br>&nbsp;&nbsp;
x:=x xor (x shr 2);<br>&nbsp;&nbsp; x:=x xor (x shr 4);<br>&nbsp;&nbsp; x:=x xor (x shr 8);<br>&nbsp;&nbsp;
x:=x xor (x shr 16);<br>&nbsp;&nbsp; writeln(x and 1);<br>end.</code><br>&nbsp;&nbsp;&nbsp;&nbsp;为了说明
上面这段代码的原理，我们还是拿1314520出来说事。1314520的二进制为101000000111011011000，第一次异或操作的结果如
下：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;00000000000101000000111011011000<br>XOR&nbsp;&nbsp;0000000000010100000011101101100<br>---------------------------------------<br>&nbsp;&nbsp;&nbsp;&nbsp;00000000000111100000100110110100<br><br>&nbsp;&nbsp;&nbsp;&nbsp;得
到的结果是一个新的二进制数，其中右起第i位上的数表示原数中第i和i+1位上有奇数个1还是偶数个1。比如，最右边那个0表示原数末两位有偶数个1，右
起第3位上的1就表示原数的这个位置和前一个位置中有奇数个1。对这个数进行第二次异或的结果如下：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;00000000000111100000100110110100<br>XOR&nbsp;&nbsp;
000000000001111000001001101101<br>---------------------------------------<br>&nbsp;&nbsp;&nbsp;&nbsp;00000000000110011000101111011001<br><br>&nbsp;&nbsp;&nbsp;&nbsp;结
果里的每个1表示原数的该位置及其前面三个位置中共有奇数个1，每个0就表示原数对应的四个位置上共偶数个1。一直做到第五次异或结束后，得到的二进制数
的最末位就表示整个32位数里有多少个1，这就是我们最终想要的答案。<br><br><br>计算二进制中的1的个数<br>&nbsp;&nbsp;&nbsp;&nbsp;同样假设x是一个
32位整数。经过下面五次赋值后，x的值就是原数的二进制表示中数字1的个数。比如，初始时x为1314520（网友抓狂：能不能换一个数啊），那么最后
x就变成了9，它表示1314520的二进制中有9个1。<br><code>x := (x and $55555555) + ((x shr 1)
and $55555555); <br>x := (x and $33333333) + ((x shr 2) and $33333333);
<br>x := (x and $0F0F0F0F) + ((x shr 4) and $0F0F0F0F); <br>x := (x and
$00FF00FF) + ((x shr 8) and $00FF00FF); <br>x := (x and $0000FFFF) +
((x shr 16) and $0000FFFF); </code><br>&nbsp;&nbsp;&nbsp;&nbsp;为了便于解说，我们下面仅说明这个程序是如何对一个8位整数进
行处理的。我们拿数字211（我们班某MM的生日）来开刀。211的二进制为11010011。<br><br>+---+---+---+---+---+---+---+---+<br>|
1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 |&nbsp;&nbsp; &lt;---原数<br>+---+---+---+---+---+---+---+---+<br>|&nbsp;&nbsp;1
0&nbsp;&nbsp;|&nbsp;&nbsp;0 1&nbsp;&nbsp;|&nbsp;&nbsp;0 0&nbsp;&nbsp;|&nbsp;&nbsp;1 0&nbsp;&nbsp;|&nbsp;&nbsp; &lt;---第一次运算后<br>+-------+-------+-------+-------+<br>|&nbsp;&nbsp;&nbsp;&nbsp;0
0 1 1&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;0 0 1 0&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; &lt;---第二次运算后<br>+---------------+---------------+<br>|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0
0 0 0 0 1 0 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; &lt;---第三次运算后，得数为5<br>+-------------------------------+<br><br>&nbsp;&nbsp;&nbsp;&nbsp;整
个程序是一个分治的思想。第一次我们把每相邻的两位加起来，得到每两位里1的个数，比如前两位10就表示原数的前两位有2个1。第二次我们继续两两相
加，10+01=11，00+10=10，得到的结果是00110010，它表示原数前4位有3个1，末4位有2个1。最后一次我们把0011和0010
加起来，得到的就是整个二进制中1的个数。程序中巧妙地使用取位和右移，比如第二行中$33333333的二进制为00110011001100&#8230;.，用
它和x做and运算就相当于以2为单位间隔取数。shr的作用就是让加法运算的相同数位对齐。<br><br><br>二分查找32位整数的前导0个数<br>&nbsp;&nbsp;&nbsp;&nbsp;这
里用的C语言，我直接Copy的Hacker's
Delight上的代码。这段代码写成C要好看些，写成Pascal的话会出现很多begin和end，搞得代码很难看。程序思想是二分查找，应该很简
单，我就不细说了。<br><code>int nlz(unsigned x)<br>{<br>&nbsp;&nbsp; int n;<br><br>&nbsp;&nbsp; if (x
== 0) return(32);<br>&nbsp;&nbsp; n = 1;<br>&nbsp;&nbsp; if ((x &gt;&gt; 16) == 0) {n = n
+16; x = x &lt;&lt;16;}<br>&nbsp;&nbsp; if ((x &gt;&gt; 24) == 0) {n = n + 8; x = x
&lt;&lt; 8;}<br>&nbsp;&nbsp; if ((x &gt;&gt; 28) == 0) {n = n + 4; x = x &lt;&lt;
4;}<br>&nbsp;&nbsp; if ((x &gt;&gt; 30) == 0) {n = n + 2; x = x &lt;&lt; 2;}<br>&nbsp;&nbsp;
n = n - (x &gt;&gt; 31);<br>&nbsp;&nbsp; return n;<br>}</code><br><br><br>只用位运算来取
绝对值<br>&nbsp;&nbsp;&nbsp;&nbsp;这是一个非常有趣的问题。大家先自己想想吧，Ctrl+A显示答案。<br>&nbsp;&nbsp;&nbsp;&nbsp;答案：假设x为32位整数，则x xor
(not (x shr 31) + 1) + x shr 31的结果是x的绝对值<br>&nbsp;&nbsp;&nbsp;&nbsp;x shr
31是二进制的最高位，它用来表示x的符号。如果它为0（x为正），则not (x shr 31) +
1等于$00000000，异或任何数结果都不变；如果最高位为1（x为负），则not (x shr 31) +
1等于$FFFFFFFF，x异或它相当于所有数位取反，异或完后再加一。<br><br><br>高低位交换<br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="http://www.vijos.cn/Problem_Show.asp?id=1201" target="_blank">这个题</a>实际上是我出的，做为学校内部NOIp模拟赛的第一题。题目是这样：<br><br>
</p>
<blockquote>&nbsp;&nbsp;&nbsp;&nbsp;给出一个小于2^32的正整数。这个数可以用一个32位的二进制数表示（不足32位用0补足）。我们称这个二进
制数的前16位为&#8220;高位&#8221;，后16位为&#8220;低位&#8221;。将它的高低位交换，我们可以得到一个新的数。试问这个新的数是多少（用十进制表示）。<br>　　例如，
数1314520用二进制表示为0000 0000 0001 0100 0000 1110 1101
1000（添加了11个前导0补足为32位），其中前16位为高位，即0000 0000 0001 0100；后16位为低位，即0000 1110
1101 1000。将它的高低位进行交换，我们得到了一个新的二进制数0000 1110 1101 1000 0000 0000 0001
0100。它即是十进制的249036820。
<p>&nbsp;</p>
</blockquote>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp;当时几乎没有人想到用一句位操作来代替冗长的程序。使用位运算的话两句话就完了。<br><code>var<br>&nbsp;&nbsp;
n:dword;<br>begin<br>&nbsp;&nbsp; readln( n );<br>&nbsp;&nbsp; writeln( (n shr 16) or
(n&nbsp;&nbsp;shl 16) );<br>end.</code><br>&nbsp;&nbsp;&nbsp;&nbsp;而事实上，Pascal有一个系统函数swap直接就可以用。<br><br><br>二
进制逆序<br>&nbsp;&nbsp;&nbsp;&nbsp;下面的程序读入一个32位整数并输出它的二进制倒序后所表示的数。<br>&nbsp;&nbsp;&nbsp;&nbsp;输入：
1314520&nbsp;&nbsp;&nbsp;&nbsp;（二进制为00000000000101000000111011011000）<br>&nbsp;&nbsp;&nbsp;&nbsp;输出：
460335104&nbsp;&nbsp;（二进制为00011011011100000010100000000000）<br><code>var<br>&nbsp;&nbsp;
x:dword;<br>begin<br>&nbsp;&nbsp; readln(x);<br>&nbsp;&nbsp; x := (x and $55555555) shl&nbsp;&nbsp;1
or (x and $AAAAAAAA) shr&nbsp;&nbsp;1;<br>&nbsp;&nbsp; x := (x and $33333333) shl&nbsp;&nbsp;2 or (x
and $CCCCCCCC) shr&nbsp;&nbsp;2;<br>&nbsp;&nbsp; x := (x and $0F0F0F0F) shl&nbsp;&nbsp;4 or (x and
$F0F0F0F0) shr&nbsp;&nbsp;4;<br>&nbsp;&nbsp; x := (x and $00FF00FF) shl&nbsp;&nbsp;8 or (x and
$FF00FF00) shr&nbsp;&nbsp;8;<br>&nbsp;&nbsp; x := (x and $0000FFFF) shl 16 or (x and
$FFFF0000) shr 16;<br>&nbsp;&nbsp; writeln(x);<br>end.</code><br>&nbsp;&nbsp;&nbsp;&nbsp;它的原理和刚才求二进制中1
的个数那个例题是大致相同的。程序首先交换每相邻两位上的数，以后把互相交换过的数看成一个整体，继续进行以2位为单位、以4位为单位的左右对换操作。我
们再次用8位整数211来演示程序执行过程：<br>+---+---+---+---+---+---+---+---+<br>| 1 | 1 | 0
| 1 | 0 | 0 | 1 | 1 |&nbsp;&nbsp; &lt;---原数<br>+---+---+---+---+---+---+---+---+<br>|&nbsp;&nbsp;1
1&nbsp;&nbsp;|&nbsp;&nbsp;1 0&nbsp;&nbsp;|&nbsp;&nbsp;0 0&nbsp;&nbsp;|&nbsp;&nbsp;1 1&nbsp;&nbsp;|&nbsp;&nbsp; &lt;---第一次运算后<br>+-------+-------+-------+-------+<br>|&nbsp;&nbsp;&nbsp;&nbsp;1
0 1 1&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;1 1 0 0&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; &lt;---第二次运算后<br>+---------------+---------------+<br>|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1
1 0 0 1 0 1 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; &lt;---第三次运算后<br>+-------------------------------+<br><br><br>Copyright
也很强<br><code>writeln('Matrix' , 42 XOR 105 , '原创，转贴请注明出处');</code></p>
<a title="Permanent link to 位运算简介及实用技巧（三）：进阶篇(2)" href="http://www.matrix67.com/blog/archives/266" rel="bookmark">位
运算简介及实用技巧（三）：进阶篇(2)</a> <br>
<p>今天我们来看两个稍微复杂一点的例子。<br><br>n皇后问题位运算版<br>&nbsp;&nbsp;&nbsp;&nbsp;n皇后问题是啥我就不说了吧，学编程的肯定都见过。下面
的十多行代码是n皇后问题的一个高效位运算程序，看到过的人都夸它牛。初始时，upperlim:=(1 shl
n)-1。主程序调用test(0,0,0)后sum的值就是n皇后总的解数。拿这个去交USACO，0.3s，暴爽。<br><code>procedure
test(row,ld,rd:longint);<br>var<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pos,p:longint;<br>begin<br><br>{
1}&nbsp;&nbsp;if row&lt;&gt;upperlim then<br>{ 2}&nbsp;&nbsp;begin<br>{ 3}&nbsp;&nbsp;&nbsp;&nbsp;
pos:=upperlim and not (row or ld or rd);<br>{ 4}&nbsp;&nbsp;&nbsp;&nbsp; while pos&lt;&gt;0
do<br>{ 5}&nbsp;&nbsp;&nbsp;&nbsp; begin<br>{ 6}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;p:=pos and -pos;<br>{
7}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pos:=pos-p;<br>{ 8}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;test(row+p,(ld+p)shl 1,(rd+p)shr
1);<br>{ 9}&nbsp;&nbsp;&nbsp;&nbsp; end;<br>{10}&nbsp;&nbsp;end<br>{11}&nbsp;&nbsp;else inc(sum);<br><br>end;</code><br>&nbsp;&nbsp;&nbsp;&nbsp;乍
一看似乎完全摸不着头脑，实际上整个程序是非常容易理解的。这里还是建议大家自己单步运行一探究竟，实在没研究出来再看下面的解说。<br><br><img alt="" src="http://www.matrix67.com/blogimage/200707261.gif" border="0">&nbsp;&nbsp;<img alt="" src="http://www.matrix67.com/blogimage/200707262.gif" border="0"><br>&nbsp;&nbsp;&nbsp;&nbsp;和
普通算法一样，这是一个递归过程，程序一行一行地寻找可以放皇后的地方。过程带三个参数，row、ld和rd，分别表示在纵列和两个对角线方向的限制条件
下这一行的哪些地方不能放。我们以6&#215;6的棋盘为例，看看程序是怎么工作的。假设现在已经递归到第四层，前三层放的子已经标在左图上了。红色、蓝色和绿色
的线分别表示三个方向上有冲突的位置，位于该行上的冲突位置就用row、ld和rd中的1来表示。把它们三个并起来，得到该行所有的禁位，取反后就得到所
有可以放的位置（用pos来表示）。前面说过-a相当于not a + 1，这里的代码第6行就相当于pos and (not pos +
1)，其结果是取出最右边的那个1。这样，p就表示该行的某个可以放子的位置，把它从pos中移除并递归调用test过程。注意递归调用时三个参数的变
化，每个参数都加上了一个禁位，但两个对角线方向的禁位对下一行的影响需要平移一位。最后，如果递归到某个时候发现row=111111了，说明六个皇后
全放进去了，此时程序从第1行跳到第11行，找到的解的个数加一。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;~~~~====~~~~=====&nbsp;&nbsp; 华丽的分割线&nbsp;&nbsp;
=====~~~~====~~~~<br><br>Gray码<br>&nbsp;&nbsp;&nbsp;&nbsp;假如我有4个潜在的GF，我需要决定最终到底和谁在一起。一个简单的办法
就是，依次和每个MM交往一段时间，最后选择给我带来的&#8220;满意度&#8221;最大的MM。但看了<a href="http://tianyi.yo2.cn/go/89403.html" target="_blank">dd牛的理论</a>后，
事情开始变得复杂了：我可以选择和多个MM在一起。这样，需要考核的状态变成了2^4=16种（当然包括0000这一状态，因为我有可能是玻璃）。现在的
问题就是，我应该用什么顺序来遍历这16种状态呢？<br>&nbsp;&nbsp;&nbsp;&nbsp;传统的做法是，用二进制数的顺序来遍历所有可能的组合。也就是说，我需要以
0000-&gt;0001-&gt;0010-&gt;0011-&gt;0100-&gt;&#8230;-&gt;1111这样的顺序对每种状态进行测试。这个
顺序很不科学，很多时候状态的转移都很耗时。比如从0111到1000时我需要暂时甩掉当前所有的3个MM，然后去把第4个MM。同时改变所有MM与我的
关系是一件何等巨大的工程啊。因此，我希望知道，是否有一种方法可以使得，从没有MM这一状态出发，每次只改变我和一个MM的关系（追或者甩），15次操
作后恰好遍历完所有可能的组合（最终状态不一定是1111）。大家自己先试一试看行不行。<br>&nbsp;&nbsp;&nbsp;&nbsp;解决这个问题的方法很巧妙。我们来说明，假如我
们已经知道了n=2时的合法遍历顺序，我们如何得到n=3的遍历顺序。显然，n=2的遍历顺序如下：<br><br>00<br>01<br>11<br>10<br><br>&nbsp;&nbsp;&nbsp;&nbsp;你
可能已经想到了如何把上面的遍历顺序扩展到n=3的情况。n=3时一共有8种状态，其中前面4个把n=2的遍历顺序照搬下来，然后把它们对称翻折下去并在
最前面加上1作为后面4个状态：<br><br>000<br>001<br>011<br>010&nbsp;&nbsp;&#8593;<br>--------<br>110&nbsp;&nbsp;&#8595;<br>111<br>101<br>100<br><br>&nbsp;&nbsp;&nbsp;&nbsp;用
这种方法得到的遍历顺序显然符合要求。首先，上面8个状态恰好是n=3时的所有8种组合，因为它们是在n=2的全部四种组合的基础上考虑选不选第3个元素
所得到的。然后我们看到，后面一半的状态应该和前面一半一样满足&#8220;相邻状态间仅一位不同&#8221;的限制，而&#8220;镜面&#8221;处则是最前面那一位数不同。再次翻折三阶遍历
顺序，我们就得到了刚才的问题的答案：<br><br>0000<br>0001<br>0011<br>0010<br>0110<br>0111<br>0101<br>0100<br>1100<br>1101<br>1111<br>1110<br>1010<br>1011<br>1001<br>1000<br><br>&nbsp;&nbsp;&nbsp;&nbsp;这
种遍历顺序作为一种编码方式存在，叫做Gray码（写个中文让蜘蛛来抓：格雷码）。它的应用范围很广。比如，n阶的Gray码相当于在n维立方体上的
Hamilton回路，因为沿着立方体上的边走一步，n维坐标中只会有一个值改变。再比如，Gray码和Hanoi塔问题等价。Gray码改变的是第几个
数，Hanoi塔就该移动哪个盘子。比如，3阶的Gray码每次改变的元素所在位置依次为1-2-1-3-1-2-1，这正好是3阶Hanoi塔每次移动
盘子编号。如果我们可以快速求出Gray码的第n个数是多少，我们就可以输出任意步数后Hanoi塔的移动步骤。现在我告诉你，Gray码的第n个数（从
0算起）是n xor (n shr 1)，你能想出来这是为什么吗？先自己想想吧。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;下面我们把二进制数和Gray码都写在下
面，可以看到左边的数异或自身右移的结果就等于右边的数。<br><br>二进制数&nbsp;&nbsp; Gray码<br>&nbsp;&nbsp; 000&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 000<br>&nbsp;&nbsp;
001&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 001<br>&nbsp;&nbsp; 010&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 011<br>&nbsp;&nbsp; 011&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 010<br>&nbsp;&nbsp; 100&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
110<br>&nbsp;&nbsp; 101&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 111<br>&nbsp;&nbsp; 110&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 101<br>&nbsp;&nbsp; 111&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 100<br><br>&nbsp;&nbsp;&nbsp;&nbsp;从
二进制数的角度看，&#8220;镜像&#8221;位置上的数即是对原数进行not运算后的结果。比如，第3个数010和倒数第3个数101的每一位都正好相反。假设这两个数分
别为x和y，那么x xor (x shr 1)和y xor (y shr
1)的结果只有一点不同：后者的首位是1，前者的首位是0。而这正好是Gray码的生成方法。这就说明了，Gray码的第n个数确实是n xor (n
shr 1)。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;今年四月份<a href="http://failedshuo.spaces.live.com/" target="_blank">mashuo</a>给
我看了<a href="http://acm.sgu.ru/problem.php?contest=0&amp;problem=249" target="_blank">这道题</a>，是二维意义上的Gray码。题目大意是说，把0到2^(n+m)-1的数写成2^n *
2^m的矩阵，使得位置相邻两数的二进制表示只有一位之差。答案其实很简单，所有数都是由m位的Gray码和n位Gray码拼接而成，需要用左移操作和
or运算完成。完整的代码如下：<br><code>var<br>&nbsp;&nbsp; x,y,m,n,u:longint;<br>begin<br>&nbsp;&nbsp;
readln(m,n);<br>&nbsp;&nbsp; for x:=0 to 1 shl m-1 do begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;u:=(x xor (x
shr 1)) shl n; //输出数的左边是一个m位的Gray码<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for y:=0 to 1 shl n-1 do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
write(u or (y xor (y shr 1)),' '); //并上一个n位Gray码<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;writeln;<br>&nbsp;&nbsp;
end;<br>end.</code><br><br>Matrix67原创<br>转贴请注明出处</p>
<a title="Permanent link to 位运算简介及实用技巧（四）：实战篇" href="http://www.matrix67.com/blog/archives/268" rel="bookmark"><strong> 位运算简介及实用技巧（四）：实战篇</strong> </a><br>下面
分享的是我自己写的三个代码，里面有些题目也是我自己出的。这些代码都是在我的Pascal时代写的，恕不提供C语言了。代码写得并不好，我只是想告诉大
家位运算在实战中的应用，包括了搜索和状态压缩DP方面的题目。其实大家可以在网上找到更多用位运算优化的题目，这里整理出一些自己写的代码，只是为了原
创系列文章的完整性。这一系列文章到这里就结束了，希望大家能有所收获。<br>&nbsp;&nbsp;&nbsp;&nbsp;Matrix67原创，转贴请注明出处。<br><br><br>
<blockquote>Problem : 费解的开关<br><br><a href="http://www.vijos.cn/Problem_Show.asp?id=1197" target="_blank"><strong> <font color="#618898">题目来源</font></strong> </a><br>&nbsp;&nbsp;&nbsp;&nbsp;06
年NOIp模拟赛（一） by Matrix67 第四题<br><br>问题描述<br>&nbsp;&nbsp;&nbsp;&nbsp;你玩过&#8220;拉灯&#8221;游戏吗？25盏灯排成一个5&#215;5的方
形。每一个灯都有一个开关，游戏者可以改变它的状态。每一步，游戏者可以改变某一个灯的状态。游戏者改变一个灯的状态会产生连锁反应：和这个灯上下左右相
邻的灯也要相应地改变其状态。<br>&nbsp;&nbsp;&nbsp;&nbsp;我们用数字&#8220;1&#8221;表示一盏开着的灯，用数字&#8220;0&#8221;表示关着的灯。下面这种状态<br><br>10111<br>01101<br>10111<br>10000<br>11011<br><br>&nbsp;&nbsp;&nbsp;&nbsp;在
改变了最左上角的灯的状态后将变成：<br><br>01111<br>11101<br>10111<br>10000<br>11011<br><br>&nbsp;&nbsp;&nbsp;&nbsp;再
改变它正中间的灯后状态将变成：<br><br>01111<br>11001<br>11001<br>10100<br>11011<br><br>&nbsp;&nbsp;&nbsp;&nbsp;给
定一些游戏的初始状态，编写程序判断游戏者是否可能在6步以内使所有的灯都变亮。<br><br>输入格式<br>&nbsp;&nbsp;&nbsp;&nbsp;第一行有一个正整数n，代表数
据中共有n个待解决的游戏初始状态。<br>&nbsp;&nbsp;&nbsp;&nbsp;以下若干行数据分为n组，每组数据有5行，每行5个字符。每组数据描述了一个游戏的初始状态。各组数
据间用一个空行分隔。<br>&nbsp;&nbsp;&nbsp;&nbsp;对于30%的数据，n&lt;=5；<br>&nbsp;&nbsp;&nbsp;&nbsp;对于100%的数据，n&lt;=500。<br><br>输
出格式<br>&nbsp;&nbsp;&nbsp;&nbsp;输出数据一共有n行，每行有一个小于等于6的整数，它表示对于输入数据中对应的游戏状态最少需要几步才能使所有灯变亮。<br>&nbsp;&nbsp;&nbsp;&nbsp;对
于某一个游戏初始状态，若6步以内无法使所有灯变亮，请输出&#8220;-1&#8221;。<br><br>样例输入<br>3<br>00111<br>01011<br>10001<br>11010<br>11100<br><br>11101<br>11101<br>11110<br>11111<br>11111<br><br>01111<br>11111<br>11111<br>11111<br>11111<br><br>样
例输出<br>3<br>2<br>-1
<p>&nbsp;</p>
</blockquote>
<p><br><br>程序代码<br><code>const<br>&nbsp;&nbsp; BigPrime=3214567;<br>&nbsp;&nbsp; MaxStep=6;<br>type<br>&nbsp;&nbsp;
pointer=^rec;<br>&nbsp;&nbsp; rec=record<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; v:longint;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
step:integer;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; next:pointer;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end;<br><br>var<br>&nbsp;&nbsp;
total:longint;<br>&nbsp;&nbsp; hash:array[0..BigPrime-1]of pointer;<br>&nbsp;&nbsp;
q:array[1..400000]of rec;<br><br>function
update(a:longint;p:integer):longint;<br>begin<br>&nbsp;&nbsp; a:=a xor (1 shl p);<br>&nbsp;&nbsp;
if p mod 5&lt;&gt;0 then a:=a xor (1 shl (p-1));<br>&nbsp;&nbsp; if (p+1) mod
5&lt;&gt;0 then a:=a xor (1 shl (p+1));<br>&nbsp;&nbsp; if p&lt;20 then a:=a xor
(1 shl (p+5));<br>&nbsp;&nbsp; if p&gt;4 then a:=a xor (1 shl (p-5));<br>&nbsp;&nbsp;
exit(a);<br>end;<br><br>function find(a:longint;step:integer):boolean;<br>var<br>&nbsp;&nbsp;
now:pointer;<br>begin<br>&nbsp;&nbsp; now:=hash[a mod BigPrime];<br>&nbsp;&nbsp; while
now&lt;&gt;nil do<br>&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if now^.v=a then exit(true);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;now:=now^.next;<br>&nbsp;&nbsp;
end;<br><br>&nbsp;&nbsp; new(now);<br>&nbsp;&nbsp; now^.v:=a;<br>&nbsp;&nbsp; now^.step:=step;<br>&nbsp;&nbsp;
now^.next:=hash[a mod BigPrime];<br>&nbsp;&nbsp; hash[a mod BigPrime]:=now;<br>&nbsp;&nbsp;
total:=total+1;<br>&nbsp;&nbsp; exit(false);<br>end;<br><br>procedure solve;<br>var<br>&nbsp;&nbsp;
p:integer;<br>&nbsp;&nbsp; close:longint=0;<br>&nbsp;&nbsp; open:longint=1;<br>begin<br>&nbsp;&nbsp;
find(1 shl 25-1,0);<br>&nbsp;&nbsp; q[1].v:=1 shl 25-1;<br>&nbsp;&nbsp; q[1].step:=0;<br>&nbsp;&nbsp;
repeat<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;inc(close);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for p:=0 to 24 do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if
not find(update(q[close].v,p),q[close].step+1) and
(q[close].step+1&lt;MaxStep) then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;open:=open+1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;q[open].v:=update(q[close].v,p);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;q[open].step:=q[close].step+1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
end;<br>&nbsp;&nbsp; until close&gt;=open;<br>end;<br><br>procedure
print(a:longint);<br>var<br>&nbsp;&nbsp; now:pointer;<br>begin<br>&nbsp;&nbsp; now:=hash[a
mod BigPrime];<br>&nbsp;&nbsp; while now&lt;&gt;nil do<br>&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if
now^.v=a then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; writeln(now^.step);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
exit;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;now:=now^.next;<br>&nbsp;&nbsp; end;<br>&nbsp;&nbsp;
writeln(-1);<br>end;<br><br>procedure main;<br>var<br>&nbsp;&nbsp; ch:char;<br>&nbsp;&nbsp;
i,j,n:integer;<br>&nbsp;&nbsp; t:longint;<br>begin<br>&nbsp;&nbsp; readln(n);<br>&nbsp;&nbsp; for i:=1
to n do<br>&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;t:=0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for j:=1 to 25 do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
read(ch);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t:=t*2+ord(ch)-48;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if j mod 5=0 then
readln;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print(t);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if i&lt;n then readln;<br>&nbsp;&nbsp;
end;<br>end;<br><br>begin<br>&nbsp;&nbsp; solve;<br>&nbsp;&nbsp; main;<br>end.</code><br><br><strong> =======================&nbsp;&nbsp;性
感的分割线&nbsp;&nbsp;=======================</strong> <br><br><br>
</p>
<blockquote>Problem : garden / 和MM逛花园<br><br>题目来源<br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="http://www.matrix67.com/blog/article.asp?id=241" target="_blank"><strong> <font color="#618898">07年Matrix67生日邀请赛</font></strong> </a>第
四题<br><br>问题描述<br>&nbsp;&nbsp;&nbsp;&nbsp;花园设计强调，简单就是美。Matrix67常去的花园有着非常简单的布局：花园的所有景点的位置都是&#8220;对
齐&#8221;了的，这些景点可以看作是平面坐标上的格点。相邻的景点之间有小路相连，这些小路全部平行于坐标轴。景点和小路组成了一个&#8220;不完整的网格&#8221;。<br>&nbsp;&nbsp;&nbsp;&nbsp;一
个典型的花园布局如左图所示。花园布局在6行4列的网格上，花园的16个景点的位置用红色标注在了图中。黑色线条表示景点间的小路，其余灰色部分实际并不
存在。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img alt="" src="http://www.matrix67.com/data/prob4.gif" border="0"><br><br>&nbsp;&nbsp;&nbsp;&nbsp;Matrix67
的生日那天，他要带着他的MM在花园里游玩。Matrix67不会带MM两次经过同一个景点，因此每个景点最多被游览一次。他和他的MM边走边聊，他们是
如此的投入以致于他们从不会&#8220;主动地拐弯&#8221;。也就是说，除非前方已没有景点或是前方的景点已经访问过，否则他们会一直往前走下去。当前方景点不存在或已游
览过时，Matrix67会带MM另选一个方向继续前进。由于景点个数有限，访问过的景点将越来越多，迟早会出现不能再走的情况（即四个方向上的相邻景点
都访问过了），此时他们将结束花园的游览。Matrix67希望知道以这种方式游览花园是否有可能遍历所有的景点。Matrix67可以选择从任意一个景
点开始游览，以任意一个景点结束。<br>&nbsp;&nbsp;&nbsp;&nbsp;在上图所示的花园布局中，一种可能的游览方式如右图所示。这种浏览方式从(1,2)出发，以(2,4)
结束，经过每个景点恰好一次。<br><br>输入格式<br>&nbsp;&nbsp;&nbsp;&nbsp;第一行输入两个用空格隔开的正整数m和n，表示花园被布局在m行n列的网格上。<br>&nbsp;&nbsp;&nbsp;&nbsp;以
下m行每行n个字符，字符&#8220;0&#8221;表示该位置没有景点，字符&#8220;1&#8221;表示对应位置有景点。这些数字之间没有空格。<br><br>输出格式<br>&nbsp;&nbsp;&nbsp;&nbsp;你
的程序需要寻找满足&#8220;不主动拐弯&#8221;性质且遍历所有景点的游览路线。<br>&nbsp;&nbsp;&nbsp;&nbsp;如果没有这样的游览路线，请输出一行&#8220;Impossible&#8221;（不带引
号，注意大小写）。<br>&nbsp;&nbsp;&nbsp;&nbsp;如果存在游览路线，请依次输出你的方案中访问的景点的坐标，每行输出一个。坐标的表示格式为&#8220;(x,y)&#8221;，代表第x
行第y列。<br>&nbsp;&nbsp;&nbsp;&nbsp;如果有多种方案，你只需要输出其中一种即可。评测系统可以判断你的方案的正确性。<br><br>样例输入<br>6 4<br>1100<br>1001<br>1111<br>1100<br>1110<br>1110<br><br>样
例输出<br>(1,2)<br>(1,1)<br>(2,1)<br>(3,1)<br>(4,1)<br>(5,1)<br>(6,1)<br>(6,2)<br>(6,3)<br>(5,3)<br>(5,2)<br>(4,2)<br>(3,2)<br>(3,3)<br>(3,4)<br>(2,4)<br><br>数
据规模<br>&nbsp;&nbsp;&nbsp;&nbsp;对于30%的数据，n,m&lt;=5；<br>&nbsp;&nbsp;&nbsp;&nbsp;对于100%的数据，n,m&lt;=10。
<p>&nbsp;</p>
</blockquote>
<p><br><br>程序代码：<br><code>program garden;<br><br>const<br>&nbsp;&nbsp;
dir:array[1..4,1..2]of integer=<br>&nbsp;&nbsp;&nbsp;&nbsp; ((1,0),(0,1),(-1,0),(0,-1));<br><br>type<br>&nbsp;&nbsp;
arr=array[1..10]of integer;<br>&nbsp;&nbsp; rec=record x,y:integer;end;<br><br>var<br>&nbsp;&nbsp;
map:array[0..11,0..11]of boolean;<br>&nbsp;&nbsp; ans:array[1..100]of rec;<br>&nbsp;&nbsp;
n,m,max:integer;<br>&nbsp;&nbsp; step:integer=1;<br>&nbsp;&nbsp; state:arr;<br><br>procedure
readp;<br>var<br>&nbsp;&nbsp; i,j:integer;<br>&nbsp;&nbsp; ch:char;<br>begin<br>&nbsp;&nbsp;
readln(m,n);<br>&nbsp;&nbsp; for i:=1 to n do<br>&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for j:=1 to m
do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; read(ch);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; map[i,j]:=(ch='1');<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
inc(max,ord( map[i,j] ))<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end;<br>&nbsp;&nbsp; readln;<br>&nbsp;&nbsp; end;<br>end;<br><br>procedure
writep;<br>var<br>&nbsp;&nbsp; i:integer;<br>begin<br>&nbsp;&nbsp; for i:=1 to step do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;writeln(
'(' , ans[i].x , ',' , ans[i].y , ')' );<br>end;<br><br>procedure
solve(x,y:integer);<br>var<br>&nbsp;&nbsp; tx,ty,d:integer;<br>&nbsp;&nbsp;
step_cache:integer;<br>&nbsp;&nbsp; state_cache:arr;<br>begin<br>&nbsp;&nbsp;
step_cache:=step;<br>&nbsp;&nbsp; state_cache:=state;<br>&nbsp;&nbsp; if step=max then<br>&nbsp;&nbsp;
begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;writep;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;exit;<br>&nbsp;&nbsp; end;<br><br>&nbsp;&nbsp; for d:=1 to 4
do<br>&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tx:=x+dir[d,1];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ty:=y+dir[d,2];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while
map[tx,ty] and ( not state[tx] and(1 shl (ty-1) )&gt;0) do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
inc(step);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ans[step].x:=tx;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ans[step].y:=ty;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
state[tx]:=state[tx] or ( 1 shl (ty-1) );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tx:=tx+dir[d,1];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
ty:=ty+dir[d,2];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tx:=tx-dir[d,1];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ty:=ty-dir[d,2];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if
(tx&lt;&gt;x) or (ty&lt;&gt;y) then solve(tx,ty);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;state:=state_cache;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;step:=step_cache;<br>&nbsp;&nbsp;
end;<br>end;<br><br>{====main====}<br>var<br>&nbsp;&nbsp; i,j:integer;<br>begin<br>&nbsp;&nbsp;
assign(input,'garden.in');<br>&nbsp;&nbsp; reset(input);<br>&nbsp;&nbsp;
assign(output,'garden.out');<br>&nbsp;&nbsp; rewrite(output);<br><br>&nbsp;&nbsp; readp;<br>&nbsp;&nbsp;
for i:=1 to n do<br>&nbsp;&nbsp; for j:=1 to m do<br>&nbsp;&nbsp;&nbsp;&nbsp; if map[i,j] then<br>&nbsp;&nbsp;&nbsp;&nbsp;
begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ans[1].x:=i;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ans[1].y:=j;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;state[i]:=1
shl (j-1);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;solve(i,j);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;state[i]:=0;<br>&nbsp;&nbsp;&nbsp;&nbsp; end;<br><br>&nbsp;&nbsp;
close(input);<br>&nbsp;&nbsp; close(output);<br>end.</code><br><br><strong> =======================&nbsp;&nbsp;性
感的分割线&nbsp;&nbsp;=======================</strong> <br><br><br>
</p>
<blockquote>Problem : cowfood / 玉米地<br><br>题目来源<br>&nbsp;&nbsp;&nbsp;&nbsp;USACO月赛<br><br>问
题描述<br>&nbsp;&nbsp;&nbsp;&nbsp;农夫约翰购买了一处肥沃的矩形牧场，分成M*N(1&lt;=M&lt;=12;
1&lt;=N&lt;=12)个格子。他想在那里的一些格子中种植美味的玉米。遗憾的是，有些格子区域的土地是贫瘠的，不能耕种。<br>&nbsp;&nbsp;&nbsp;&nbsp;精明
的约翰知道奶牛们进食时不喜欢和别的牛相邻，所以一旦在一个格子中种植玉米，那么他就不会在相邻的格子中种植，即没有两个被选中的格子拥有公共边。他还没
有最终确定哪些格子要选择种植玉米。<br>&nbsp;&nbsp;&nbsp;&nbsp;作为一个思想开明的人，农夫约翰希望考虑所有可行的选择格子种植方案。由于太开明，他还考虑一个格子
都不选择的种植方案！请帮助农夫约翰确定种植方案总数。<br><br>输入格式:<br>&nbsp;&nbsp;&nbsp;&nbsp;第一行：两个用空格分隔的整数M和N<br>&nbsp;&nbsp;&nbsp;&nbsp;第
二行到第M+1行：第i+1行描述牧场第i行每个格子的情况，N个用空格分隔的整数，表示这个格子是否可以种植（1表示肥沃的、适合种植，0表示贫瘠的、
不可种植）<br><br>输出格式<br>&nbsp;&nbsp;&nbsp;&nbsp;一个整数，农夫约翰可选择的方案总数除以 100,000,000 的余数<br><br>样例输入<br>2
3<br>1 1 1<br>0 1 0<br><br>样例输出<br>9<br><br>样例说明<br><br>&nbsp;&nbsp;&nbsp;&nbsp;给可以种植玉米的格子编
号：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1 2 3<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;4<br><br>&nbsp;&nbsp;&nbsp;&nbsp;只
种一个格子的方案有四种(1,2,3或4)，种植两个格子的方案有三种(13,14或34)，种植三个格子的方案有一种(134)，还有一种什么格子都不
种。<br>&nbsp;&nbsp;&nbsp;&nbsp;4+3+1+1=9。<br><br>数据规模<br>&nbsp;&nbsp;&nbsp;&nbsp;对于30%的数据，N,M&lt;=4；<br>&nbsp;&nbsp;&nbsp;&nbsp;对于
100%的数据，N,M&lt;=12。
<p>&nbsp;</p>
</blockquote>
<p><br><br>程序代码：<br><code>program cowfood;<br><br>const<br>&nbsp;&nbsp;
d=100000000;<br>&nbsp;&nbsp; MaxN=12;<br><br>var<br>&nbsp;&nbsp; f:array[0..MaxN,1..2000]of
longint;<br>&nbsp;&nbsp; w:array[1..2000,1..2000]of boolean;<br>&nbsp;&nbsp;
st:array[0..2000]of integer;<br>&nbsp;&nbsp; map:array[0..MaxN]of integer;<br>&nbsp;&nbsp;
m,n:integer;<br><br>function Impossible(a:integer):boolean;<br>var<br>&nbsp;&nbsp;
i:integer;<br>&nbsp;&nbsp; flag:boolean=false;<br>begin<br>&nbsp;&nbsp; for i:=1 to MaxN do<br>&nbsp;&nbsp;
begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if flag and (a and 1=1) then exit(true);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;flag:=(a
and 1=1);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;a:=a shr 1;<br>&nbsp;&nbsp; end;<br>&nbsp;&nbsp; exit(false);<br>end;<br><br>function
Conflict(a,b:integer):boolean;<br>var<br>&nbsp;&nbsp; i:integer;<br>begin<br>&nbsp;&nbsp;
for i:=1 to MaxN do<br>&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (a and 1=1) and (b and 1=1)
then exit(true);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;a:=a shr 1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;b:=b shr 1;<br>&nbsp;&nbsp; end;<br>&nbsp;&nbsp;
exit(false);<br>end;<br><br>function CanPlace(a,b:integer):boolean;<br>begin<br>&nbsp;&nbsp;
exit(a or b=b);<br>end;<br><br>procedure FindSt;<br>var<br>&nbsp;&nbsp;
i:integer;<br>begin<br>&nbsp;&nbsp; for i:=0 to 1 shl MaxN-1 do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if not
Impossible(i) then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; inc(st[0]);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
st[st[0]]:=i;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end;<br>end;<br><br>procedure Init;<br>var<br>&nbsp;&nbsp;
i,j:integer; <br>begin<br>&nbsp;&nbsp; for i:=1 to st[0] do<br>&nbsp;&nbsp; for j:=i to
st[0] do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if not Conflict(st[i],st[j]) then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
w[i,j]:=true;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; w[j,i]:=true;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end;<br>end;<br><br>procedure
Readp;<br>var<br>&nbsp;&nbsp; i,j,t,v:integer;<br>begin<br>&nbsp;&nbsp; readln(m,n);<br>&nbsp;&nbsp;
for i:=1 to m do<br>&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;v:=0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for j:=1 to n do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
read(t);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; v:=v*2+t;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map[i]:=v;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;readln;<br>&nbsp;&nbsp;
end;<br>end;<br><br>procedure Solve;<br>var<br>&nbsp;&nbsp; i,j,k:integer;<br>begin<br>&nbsp;&nbsp;
f[0,1]:=1;<br>&nbsp;&nbsp; map[0]:=1 shl n-1;<br>&nbsp;&nbsp; for i:=1 to m do<br>&nbsp;&nbsp; for
j:=1 to st[0] do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if not CanPlace(st[j],map[i]) then f[i,j]:=-1
else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for k:=1 to st[0] do if (f[i-1,k]&lt;&gt;-1) and w[j,k]
then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f[i,j]:=(f[i,j]+f[i-1,k]) mod d;<br>end;<br><br>procedure
Writep;<br>var<br>&nbsp;&nbsp; j:integer;<br>&nbsp;&nbsp; ans:longint=0;<br>begin<br>&nbsp;&nbsp; for
j:=1 to st[0] do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if f[m,j]&lt;&gt;-1 then ans:=(ans+f[m,j])
mod d;<br>&nbsp;&nbsp; writeln(ans);<br>end;<br><br>begin<br>&nbsp;&nbsp;
assign(input,'cowfood.in');<br>&nbsp;&nbsp; reset(input);<br>&nbsp;&nbsp;
assign(output,'cowfood.out');<br>&nbsp;&nbsp; rewrite(output);<br><br>&nbsp;&nbsp; FindSt;<br>&nbsp;&nbsp;
Init;<br>&nbsp;&nbsp; Readp;<br>&nbsp;&nbsp; Solve;<br>&nbsp;&nbsp; Writep;<br><br>&nbsp;&nbsp; close(input);<br>&nbsp;&nbsp;
close(output);<br>end.</code></p>
<br>  <img src ="http://www.cppblog.com/yuqilin1228/aggbug/110616.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yuqilin1228/" target="_blank">LynnRaymond</a> 2010-03-26 21:48 <a href="http://www.cppblog.com/yuqilin1228/archive/2010/03/26/110616.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>