左传、国语叙事出入一例·医和视疾(2009-07-12 16:09)
前一阵在图书馆翻《公羊传》,见其记事与《左传》多有出入,念及古文经与今文经本就势成水火,心下也就释然。今天入手了《国语》,此书谓之左丘明所作大抵也是托名,不过看到与《左传》的冲突之处还是有些怃然。
今日所见之例为,《晋语八·医和视疾》中,医和为晋平公诊视之后道:“是所谓远男而近女,惑以生蛊;非鬼非食,惑以丧志。良臣不生,天命不佑。若君不死,必失诸侯。”其后赵武问自己政绩如此,怎么算“天命不佑”,医和直言赵武不能谏君,“使至于生疾,又不自退以宠于政,八年之谓多矣,何以能久。”
《左传》中的相应记载在昭公元年,大体事件是一致的,关键就在于赵武的反应,赵武不是质问天命如何不佑,而是询问命不久矣的良臣指谁,医和回答良臣就是赵武,恭维了一番赵武任正卿八年的政绩,接着指出其过失:“国之大臣,荣其宠禄,任其宠节,有灾祸兴而无改焉,必受其咎。”
两段文字塑造的赵武与医和的形象大不相同。左传中赵武一向是敦厚、老成、低调的形象,此处也不例外地不以良臣自居,与国语里自争为良臣判若两人。而医和的形象在左传中也要含蓄的多,国语中几乎是直接不留情面的批评,而在左传中是先肯定其政绩,后指出问题,可以说烘托出的赵武的形象也更为正面。
之前已经买了4月30号回去的火车票,今天照旧踩提前十天的点,去清华园站买5月4号回来的返程票。预案是如果买不到聊城到北京西的,就买路过济南的动车组,当时得知新添了泰安始发的动车很是兴奋,心想总算有保底的方案了,虽然会贵不少。
六点四十方才吃完晚饭,到东主楼的时候意识到身上的钱可能不够,能买到特快固然没事,买不到的话俩人加起来未必够一张动车的。正在犹豫去哪里找取款机,见Crucial路过,索性把他身上两百块钱都搜刮来了。
到清华园站的时候队伍倒是不像前次买票那么长,想来是动念买返程票的人不多的缘故,前面排的也大多是买五一之前的票的。排到我们之后一问,聊城的车果然都没票了,小站预留的票实在少得可怜,再问济南的,查了几次终于查到D40还有票,10点19从济南出发,算一算时间还算合适,也不用太早从家里出发。两张票连手续费一共316,买完之后兜里还剩二十来块钱,心疼之余庆幸不已,还好遇见的活提款机身上的钱正好够。
回来一查,D40原来是济南始发的,济南始发到北京南的动车居然有D36、D38、D40、D42四趟,时间从早上七点绵延至傍晚六点,贵则贵矣,倒确实方便了不少。中国铁路这么转型下去,用昂贵而稠密的动车组逐步取代普快和特快们,我终于开始相信若干年之后春运不会再这么挤了。这次正好体验一下北京南站的公共交通有没有长进。
猜背后数字的智力题(2009-04-24 10:51)
据说是未名十大,我没有亲见,就不给出处了,问题描述也简短解说。
问题:16个人背后各贴一个数字,每个数字都可能取1到16的任意整数值(即可以重复,可以有数字不出现),每个人可以看到其他人的数字,但看不到自己的,贴好数字之后不可以进行任何形式的交流(诸如使用排队次序传达信息之类的伎俩无效),每人猜一个数字,问事先如何安排可以保证至少有一人猜中自己的数字。
分析:
在这种变态的条件下,都猜0肯定不行了,观察其他数的分布也不能保证必中,某一人英雄地违反规则喊出别人的数也没什么意义,这是个数学问题不是社会学问题。
此题的关键在于“按位异或”的神奇性质。
异或(以下用^表示)的规则:1^1=0, 1^0=1, 0^1=1, 0^0=0。
这个规则大概够格的信息学院学生都能默出来(我不在此列,sigh),不过就是“相同为假,相异为真”而已,但异或的特别之处在于下面的性质:a^b=c
=> a^c=b,就是说俩操作数和一个结果是可以任意交换的,观察一下上面的四条规则,是不是都符合?
当当当,答案在此:每个人事先分一个编号,从0到15,记为i;每人身后贴的数字再减1记为Ai(减1是为了保证此数落在0~15范围内,即可以用4位二进制数表示);A0~A15的按位异或结果记为X,显然X也是4位二进制数,也保证0<=X<=15;除去Ai的其他15个数的按位异或结果记为X_Ai,所以有X=X_Ai
^ Ai;编号为i的人猜的数字是他能看见的其他15个数字与自己的编号的异或再加1,即上报(X_Ai ^ i)+1。
为什么这个安排能保证至少一人成功呢?因为X=X_Ai ^
Ai,所以X_Ai=X^Ai(上面说的异或的特殊性质),所以i号同学上报的其实是(X^Ai^i)+1,虽然他并不知道X与Ai分别是什么。
而X是一个0到15之间的整数,i从0取到15,所以总会存在一个同学(假设为j号同学)的编号与X恰好相等(j==X),他所猜的就是(j^Aj^j)+1。j^j会产生什么?0000。Aj^0000会产生什么?Aj自己。所以幸运的j号同学上报的数字就是Aj+1,即自己背后贴的数字。中奖的人有且只有一个。当然拿到的奖必须平分。
//多叉树中,将没有子节点的节点称为“芽”,如果某个节点的子节点中有多于一个的芽,则删除一个
//编写函数计算一棵树中需要删除的芽的个数
//多叉树的表示和构造方法都比较恶心,看能不能想出好点的主意
#include <stdio.h>
#include <queue>
using std::queue;
struct Node
{
int child_num;
Node ** child;
Node(int n, Node ** children):child_num(n),
child(children) {}
};
int count_cut(Node * A)
{
queue <Node*> tree;
Node * p;
int child_count;
int leave_count;
int cut_num = 0;
tree.push(A);
while(!tree.empty())
{
p = tree.front();
tree.pop();
child_count = 0;
leave_count = 0;
while(child_count <
p->child_num)
{
if(p->child[child_count]->child_num == 0)
leave_count++;
else
tree.push(p->child[child_count]);
child_count++;
}
if(leave_count > 1)
cut_num++;
}
return cut_num;
}
int main()
{
Node * D = new Node(0, 0);
Node * E = new Node(0, 0);
Node * B_child[] = {D, E};
Node * B = new Node(2, B_child);
Node * K = new Node(0, 0);
Node * L = new Node(0, 0);
Node * J_child[] = {K, L};
Node * J = new Node(2, J_child);
Node * G_child[] = {J};
Node * F = new Node(0, 0);
Node * G = new Node(1, G_child);
Node * H = new Node(0, 0);
Node * I = new Node(0, 0);
Node * C_child[] = {F, G, H, I};
Node * C = new Node(4, C_child);
Node * A_child[] = {B, C};
Node * A = new Node(2, A_child);
printf('%d\n', count_cut(A));
}
干掉Google自动更新进程的方法(2009-04-12 21:52)
得知这办法有一阵了,刚看到Google为证明它的自动更新进程人畜无害而将Google
Update开源了,想起来该把这个记一记。姑且立此存照,以鉴其强横。
如今在安装Google多数软件(地球、输入法、浏览器、桌面搜索……)之后,进程项里都会多几个附骨之蛆一般的Google
Update进程,纵使我相信Google不会下作到拿这玩意收集用户隐私,看着也是心烦不已。后来见人介绍对付搜狗拼音自动更新的经验时说在“控制面板-任务计划”里删除对应项即可,一看Google的两项自动更新也在里面,于是照猫画虎一番,心下舒坦了不少。
不料后来想起来查一下进程,google的几个更新还在里面,再看任务计划里果然又冒出来了,看来google确实比搜狗技高一筹。这次想起来再去看看系统服务里的状况,打开services.msc,将与Google有关的都禁用掉,总算是消停了。
不过我印象里早先见这个自动更新阴魂不散的时候,就已经把它的服务关掉了,只是因为不知道这东西还埋伏在任务计划里才没有奏效,为何此时服务又打开了呢?是之后又安装Google其他软件时开启的,还是任务计划里打开的,或者就是我记错了呢?且不去管它了,总之知道了干掉它的万全办法就好。
哥们做的用txt文件做jar电子书的程序,具体介绍在这里,在这里下载,摘要如下:
======
【设计需求】
- 可以支持足够大的txt文件,只要手机能够存放下的文件,那么就能够读取这么大的文件,而且性能上不会有任何损失。
- 能够快速的定位到txt文件的任何位置,无论是向上翻页,向下翻页,还是跳转,都是非常灵活自如的。
- (制作软件)跨平台支持,简单易用的命令行格式。
【软件特性】
- 支持绝大多数手机(只要手机支持Java,它就支持这部手机)。
- 快速阅读,无论如何翻页,跳转,都能够以很快的速度读取所要的文字。
- 支持跳转功能。
- 退出保存上次阅读章节和位置,并且每一章的阅读位置都会被保存,方便再次读取。
- 支持书签功能,可以加入书签,修改书签,跳转到书签所在位置。
- 支持文字搜索功能,可以搜索指定区间内的文字,支持跳转到搜索结果处。
- 很好的文本格式控制,能够正确的显示空格,回车,换行,提升阅读体验。
- 可以设置字体大小,颜色,背景色等等。
- 进度条实时指示文章阅读位置。
- 当然,最重要的,它完全的解决了上面我提出的3个问题,特别是支持大文件哦。
【request for comments】
因为是刚刚写成的软件,测试还很不充分,所以我希望每一个喜欢这个软件的人,能够提出自己的意见,特别是那些发现了bug的人,我很需要很需要你们的支持,请务必告诉我你所发现的bug,能帮我把它改进的更好。大家可以发邮件给我(注:不是我)thelastender@gmail.com,标题里注明和
eBookGenerator相关就可以了: ) 。
======
整数拆分方法数算法改进(2009-03-24 10:33)
之前的方法中恶心的调用次数里充斥着大量的重复计算,只要牺牲一点空间,把计算过的count(n,m)的结果储存起来,必然可以大大降低运算次数。
如果使用一个线性表存储,需要存储的各项分别为count(2,1), (3,1), (4,1),
(4,2), (5,1), (5,2),
(6,1)……简单推算一下,count(n,m)的下标可以表示为(n-(n/2)-1)*(n/2)+m-1。可以用vector实现这个需要动态增长的线性表。
修改后的代码如下:
#include <iostream>
#include <vector>
using namespace std;
vector<int> count_cache;
int cache_hit = 0;
int count_call = 0;
int count(int n, int m)
{
count_call ++;
if(n < m*2)
{
return 0;
}
int l = n/2;
int index = (n - l - 1)*l + m - 1;
for(int i = count_cache.size(); i <= index;
i++)
{
count_cache.push_back(0);
}
if(count_cache[index] != 0)
{
cache_hit ++;
return
count_cache[index];
}
int val = 1;
int j = (n - m)/2;
for(int i = m; i <= j; i++)
{
val += count(n-m, i);
}
count_cache[index] = val;;
return val;
}
int count(int n)
{
int val = 0;
int j = n/2;
for(int i = 1; i <= j; i++)
{
val += count(n, i);
}
return val;
}
int main(void)
{
int n;
cin >> n;
cout << count(n) <<
endl;
cout << count_call << ' ' <<
cache_hit << ' ' << count_cache.size() <<
endl;
}
实测一下,30的拆分方法有5603种,计算时调用count(n,m)
800次,其中cache命中575次,vector长度增长到225;50的拆分方法204225种,调用3597次,命中2972次,vector长度625;100的拆分方法190569291种,调用28236次,命中25736次,vector长度2500。总算是把时间复杂度降到O(n^2)了。
整数的拆分方法个数(2009-03-23 22:29)
在百度知道上看见一个有意思的提问,着手试了一下,姑且放到这里,不知道以后能不能想到更好的办法。
【题目】将一个正整数拆分成若干个正整数的和,问有多少种分法。
【题解】2有1+1一种分法,3有1+1+1和1+2两种分法,4有1+1+1+1、1+1+2、1+3和2+2四种分法。对每一个正整数n,它的拆分式可以归为若干类:最小项为1者,最小项为2者,……,最小项为n/2者。
最小项为1的拆分式将1个1拿掉后,恰为n-1的所有拆分式以及n-1本身;最小项为2的拆分式将1个2拿掉后,为n-2的所有分式中去掉最小项为1的拆分式,再加上n-2本身。
如果用count(n,m)表示n的最小项为m的拆分式个数,那么有count(n,m)=count(n-m,m)+count(n-m,m+1)+count(n-m,m+2)+……+count(n-m,(n-m)/2)+1。于是求一个数的拆分式的个数的问题就降为求若干个更小的数的拆分式个数问题,直至count(n,m)的调用中n小于2m,此次调用返回0为止。即该问题可以用递归实现。
调用次数是很惊人的,每调用一次才能给结果加1,所以有多少种拆分式就要调用多少次,10有41种分法,20有626种,30有5603种,50就有204225种了。暂时没想出来复杂度怎么表示。
【代码】
#include
<iostream>
using namespace std;
int count(int n, int a)
{
if(n < a*2)
{
return 0;
}
int val = 1;
int j = (n - a)/2;
for(int i = a; i <= j; i++)
{
val += count(n-a, i);
}
return val;
}
int count(int n)
{
int val = 0;
int j = n/2;
for(int i = 1; i <= j; i++)
{
val += count(n, i);
}
return val;
}
int main(void)
{
int n;
cin >> n;
cout << count(n) << endl;
}
子网掩码错误导致的一个古老案例浅析(2009-03-18 00:15)
这事情已经很久远了,还是去年夏天Free打游击的时候,只是当时不甚了然,今天看到一些关于子网掩码的资料,才总算知道到底怎么回事。
记不清是不是服务器刚搬回机房的那一天,总之管理员要重新设置Free的地址,在ifconfig的时候忘了加netmask选项,结果那台机器的子网掩码就被设成了默认的255.0.0.0。引发的症状在当时看来十分诡异:26#的人可以访问,校外也可以访问,紫荆却不能访问,我在宿舍也不能访问。现在回头看,当时在实验室的人应该是可以连上的,可惜对此没有确证的印象了。
原理其实很简单。当26#之外的宿舍区的人试图连接free时,free服务器就会看到IP地址是59.66.c.d
(c!=122)的主机向它发包,而服务器的掩码被设为255.0.0.0,于是它认为这个主机和它在同一个子网内(事实上,所有第一节是59的IP地址都会被认它成同一个子网的兄弟),就不用劳烦网关转发了,它亲自向子网内发出arp广播:“who
has
59.66.c.d”,希望找到对方后通过MAC地址直接与之二层通信。而能收到这个广播包的所有机器的地址第三节都是122,自然不会回应这个广播。服务器收不到回应,只得放弃寻找这个主机,这一次连接就彻底中断了。至于26#的主机,虽然服务器把它认成兄弟的理由不大对,但结果还是没有问题的,依然可以如常进行二层通信。而对于实验室以及校外的主机,它们不会被服务器错误的套近乎(除了59.*.*.*的之外),服务器依然会老老实实把包交给网关转发,因此访问还是可以正常进行。
在条款7“为多态基类声明virtual析构函数”中,提到类中包含virtual函数会使对象大小增加,以包含两个int的Point
class为例:“在32-bit计算机体系结构中将占用64
bits(为了存放两个ints)至96 bits(两个ints加上vptr);在64-bit计算机体系结构中可能占用64~128
bits,因为指针在这样的计算机结构中占64
bits。”照此说法,32位机中含有virtual函数的Point类的大小会是分布在64位到96位范围内的一个“变量”,而事实上它毫无疑问的会是96位。我怀疑原文的表述应该是32位机中Point类的占用空间将“从64
bits增加到96 bits”,同理64位机中是从64 bits增至128 bits。
在网上没找到第三版的英文版,费半天劲专门注册了一个很慢的网站下载到的电子书却是第二版的,对应的Item 14中这一段的表述如下:“What is
important is that if the Point class contains a virtual function,
objects of that type will implicitly double in size, from two
16-bit shorts to two 16-bit shorts plus a 32-bit vptr!
”当时Point类里存放的是两个短整型,而且64位机的情况还没进入考虑范畴(第二版是97年写的),至于表述还是很清楚的:大小会翻一倍,从32位增长到64位。大概就是“from...to...”的结构导致的这处误译。