摘要:编程之美2.8节。大略看了一下作者的解答。作者的解法有点动态规划的套路。这里尝试用广搜的办法来求解。在我们做乘法时,最低位的数字决定于乘数的最低位,而与乘数的高位无关。广搜的策略就是从低到高逐位尝试,直到找到结果。注意下面的代码中vis数组的使用。这个数组可以使广搜树得到有效剪枝。本来STL中vector<bool>的特化是被很多人批评的一件事,这里却恰是我想要的结果,不仅速度快,还可以有效节省空间。struct Item { int v; string s; Item(int vv, const string& ss): v(vv), s(ss) {}};bool bfs
阅读全文
摘要:这与编程之美的4.5节磁带文件的存放优化有关。原题要求使得所有的平均访问长度最短,而存放序列是线性的。事实上,这是一个排序问题。我们从排序问题本身来看。假设我们已经有一个序列:58976231。我们的目标是使得由这些数字构成的值最小。这是一个显然的排序问题。不过,这里换一个角度来看待它。我们采用局部优化的方式来解决这个问题。首先,对于任意相邻的两个数字ab,如果a < b,显然就有 ab < ba。为了使问题能更一般化一些,假设无法进行简单的a,b比较, 我们如何再进行排序呢?比如说在原题的情况下,我们就无法简单对两个文件进行比较。换一个思路,考虑这两个文件构成的序列,我们是可以评
阅读全文
摘要:编程之美1.4节。这是一道很有趣的问题。作者试图使用贪心策略去解答,尽管给出了贪心中关键的变化,不过最终并没有给出证明。然后,解法二中使用了动态规划来求解这道题。另外,新版中提到薛笛有更细致一点的分析:http://blog.csdn.net/kabini/archive/2008/04/16/2296943.aspx,其中主要讨论了贪心算法。不过,我感觉仍然不够简明清晰。确实,这道题是可以使用贪心的。关键是如何应用贪心策略,从而保证可以到达最优解。下面给出我的理解。首先,类似于作者的思路,可以试图分解较小的数找到最佳的分解组合。不过,这里作者和薛笛关注的方向不恰当。事实上,对于任意的组合数,
阅读全文
摘要:编程之美2.11节。也可参考:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=113&page=show_problem&problem=1186对于扩展问题2,可以先找到这些点的凸包(O(nlogn)),然后对凸包点求最大距离。对于凸包点,可以考虑按长轴平分四份,然后在最外侧的两份之间寻找最大点对。因为居内侧的两份中的最大距离无法超过长轴。当然,可以分割得更精细一点。这只是计算上复杂性的小差别了。struct Point { double x, y;};
阅读全文
摘要:编程之美2.12节。可参考http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=113&page=show_problem&problem=1428关于扩展问题,先考虑三个数的情形:首先排序,如果所有数都小于sum/3或者大于sum/3,那么肯定不会有解。不妨考虑存在小于等于sum/3的情形,可以肯定<=sum/3的数中必定要取一个。在取定了一个<=sum/3的数之后,我们可以在大于这个数的范围内寻找剩下的两个数,这时问题即转化为了初始问题。对于任意
阅读全文
摘要:编程之美的作者把3.2节的难度设定为三星,私下以为在二星左右。书中的解法二即最简版的深搜实现。而解法一实际是在枚举。事实上,这个问题十分类似于anagram问题。关于anagram问题,编程珠玑上有很细致的讨论。如果有兴趣的话,也可以尝试下面这道题:http://poj.org/problem?id=1002关于枚举的实现,我还是更喜欢实现一个类似于next_permutation的方法,然后用do...while循环来遍历。bool next_number(vector<int>& digits, const vector<int>& ranges)
阅读全文
摘要:这是一个有趣的问题。编程之美1.7节进行了探讨。其中解法二的思路非常巧妙。其用到的找逆序数算法是merge sort的变形,也很常见。最后的代码是这个算法的一份实现。这里则要从一个不同的角度来考虑这个问题。作者在分析分割的区域时,得到了这样的公式: F = N+M+1,其中N为直线数,M为交点数。作者的思路简明清晰。这里则要采用另外一个思路来给出这个公式。考虑著名的欧拉公式:F = E - V + 2。我们可以假想两条垂线在无穷远处重合,从而构成一个长轴是无穷大的椭圆区域,如下面的图所示。只考虑区域内的线段,再将区域外想象成为一个面,则整个图形构成一个压平的多面体。其顶点就是这些线条的交点,边
阅读全文
摘要:编程之美2.19节,写了一份实现。对于扩展问题,可以使用一个平面四叉树来维护被覆盖的部分,对于每一个查询的矩形,在树中查询是否有空隙。这个过程同建树的过程中,插入一个新矩形是一致的。在建四叉树的时候,注意合并那些完全覆盖的子树成为叶节点。当然,我觉得用k-d树来维护覆盖信息也可以。template<class T>struct Interval { T a, b; bool operator<(const Interval& other) const { return a < other.a || a == other.a && b > o
阅读全文
摘要:关于最短摘要,编程之美3.5节并没有给出详细的代码。其实还是值得一写的。下面的实现我使用了set和map来维护相关的信息。int main() { int n, m; while (cin>>n>>m) { // input vector<string> seq; while (n--) *back_inserter(seq) = *istream_iterator<string>(cin); set<string> kwords; while (m--) *inserter(kwords, kwords.end()) = *istr
阅读全文
摘要:编程之美2.18节。作者的代码很精练,可惜解释不太清晰。虽然代码只有三个for语句,可是头两个for语句不可以随意置换。而且,第二个for语句必须是以递减的方式计算。取i个元素求和,把这些和构成一个集合。我们只记录<=sum/2的和,则最多可以获得n/2个集合S(i)。在构造S(i)的时候,可以使用S(i-1)中的和加上一个未使用的元素v[k]来构造。每多一个新元素,这些集合就可以扩张一次,这两个for循环的含义也即在此。如果使用set来维护这些集合,由于自动忽略了重复的元素,算法实际上并不会达到O(2^n)的复杂度,最多为O(n^2*sum),即解法三的复杂度。而且,由于使用的是set
阅读全文
摘要:编程之美2.14, 2.15节。这个问题在编程珠玑中的算法章有很深入的分析。可以试解下题:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=113&page=show_problem&problem=448http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=113&page=show_problem&problem=44ht
阅读全文
摘要:编程之美2.16节。较容易想到的方法是O(n^2)的。这里有一个细节没有使用到,假设记录下所有长度的子序列结果,那么我们可以保证长度为n的子序列尾元素一定大于等于长度为n-1的子序列尾元素,否则的话,我们可以从长度为n的子序列中获取一个n-1的子序列,将之前的结果覆盖。利用这个性质,我们可以获得一个O(nlogn)的算法:template<class It>vector<typename iterator_traits<It>::value_type>lis(It first, It last) { typedef iterator_traits<It
阅读全文
摘要:任给一棵二叉树合法的两种遍历结果,就可以将它重建出来。事实上,不需重建这棵树,而进行隐式的遍历。可以尝试下面的这道题目:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=104&page=show_problem&problem=489对于编程之美3.9节的这个问题及其扩展问题,简单的实现代码如下:对扩展问题1,显然是不唯一的,考虑所有节点都标注a的极端情况即知。实现方面,需要把下面的find查找修改为直到没有匹配为止,然后分别尝试重建。template &
阅读全文
摘要:编程之美3.8。这题让我想起了另外一道著名的题目:lowest common root. 两题都是典型的树之上的DP。下面为实现:struct Node { int v; Node *left, *right;};size_t diameter(Node* root, size_t& depth) { depth = 0; if (!root) return 0; size_t ldepth, rdepth; size_t ldiameter = diameter(root->left, ldepth); size_t rdiameter = diameter(root->
阅读全文
摘要:是的,这个使用BFS可以很简洁地实现。不过编程之美3.10节有两个扩展问题。对于第二个问题,用BFS就不那么简便了,相反递归在这里更合适。另一方面,对于初始问题,不一定要使用queue,虽然空间上略微浪费一些(最大为其两倍),但实现代码可以同第二个扩展问题有部分共享。下面的代码,注意swap的使用。初看以为这里会很浪费时间,实际上STL的swap实现是O(1)的。另,关于第二个扩展问题,可以联系到下面的题目:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=103&
阅读全文
摘要:编程之美2.7节。解法三即stein算法。下面是其迭代式实现。template<class T>T gcdStein(T a, T b) { for(T r = 1;;) { if (!a) return r *= b; if (!b) return r *= a; T o1 = 0; while (!(a&1)) a>>=1, ++o1; T o2 = 0; while (!(b&1)) b>>=1, ++o2; r <<= min(o1, o2); if (a == 1 || b == 1) return r; if (a &
阅读全文
摘要:下面的代码尝试解决带环和不带环的链表是否相交以及如果相交,交点的位置两个问题。见编程之美3.6节,代码应当同时解决了后面的两个扩展问题。Node* commonEnd(Node* a, Node* b) { if (!a || !b) return 0; Node *p1 = a, *p2 = a; do { if (p2->next) p2 = p2->next; if (p2->next) p2 = p2->next; if (p1->next) p1 = p1->next; } while (p1 != p2); Node *q1 = b, *q2 =
阅读全文
摘要:编程之美4.7节。参考下题:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=113&page=show_problem&problem=1655int main() { vector<int> positions; int t, len, n; cin>>t; while (t--) { cin>>len>>n; positions.clear(); while (n--) *back_inserter(po
阅读全文
摘要:暴力搜索是一个有趣的东西。至少刘汝佳是这么认为的。编程之美的4.10节就是典型的暴力题。虽然作者将其难度定义为一颗星,但却不能因此认为这个类型的问题就是那么容易的,很多可能需要一些有创造力的想法。不妨试试下面这几道题:Island of Logic:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=107&page=show_problem&problem=533Meta-loopless sort:http://uva.onlinejudge.org/in
阅读全文
摘要:编程之美的第3.7节。作者使用了堆和max stack来实现。其实,第二种方法是可以借鉴到queue上面去的。实现上的差别是push的复杂度不是O(1),而pop则是O(1)的。template<class T, class C = deque<T> >class max_queue : queue<T, C> { deque<T> mq_;public: const T& max() const { return const_cast<max_queue<T, C>*>(this)->max(); } T&
阅读全文