Jin's profileFlying DreamPhotosBlogLists Tools Help
Photo 1 of 10

Flying Dream

March 09

NTA(1011)

The NTA (Non-deterministic Tree Automata) is a kind of tree structure device. The device is built in a set of operating rules. With these rules the device can produce several signals, which will form a signal system. In such a system, one signal is the starting signal, several signals are the acceptable signals, and the others are the auxiliary ones. A pair of signals is said to be an acceptable pair if both two signals of the pair are acceptable.

The trees discussed here are all binary trees. Every non-leaf node has two successors. In any finite tree, each node has a signal-transmitting element. When a signal arrives at one node, the signal meets the signal transmitting substance, and triggers off signal reactions, which will produce several pairs of signals. Then the device selects a pair of signals non-deterministically and sends them to its successors. The first signal in the signal pair is sent to the left successive node and the second one is sent to the right successive node.

The whole operation for an NTA is as follows:

The device first sends the starting signal to the root node. According to the signal transmitting substance at the root node, the device selects a pair of signals non-deterministically and sends the first to the left son and the second to the right son. Each of the two signals then meets the signal transmitting substance at the corresponding node and produces another two signals. The course proceeds down until the signals arrive at the leaves.

If a signal reaches one leaf and the leaf can produce a pair of acceptable signals, we say the leaf is "shakable". A transmission of signals from the root to leaves is said to be valid if all leaves are "shakable". A tree structure with signal transmitting substance is valid if there exists such a valid transmission. A tree is invalid if all the transmissions are invalid.

For simplicity, we denote the signal transmitting elements by consecutive lowercase letters "a", "b", "c", etc.. The signals of an NTA are consecutive numbers 0,1,2, ..., and so on. The first signal 0 is always a starting signal. Thus the signals for a 4-signal NTA are "0" "1" "2" and "3". Accepting signals are arranged at the end of the number sequence so that if a 4-signal NTA has two accepting signals, the accepting signals are "2" and "3". The transition rules of signals are based on a transition table. For example, the following table describes a transition table with four signals "0", "1", "2", "3" and with three signal transmitting elements "a", "b" and "c".

 

T a b c
0 (1,2) (2,1) (1,0)
1 (2,2) (0,2),(1,0) (3,2)
2 (2,2) (2,3) (1,2)
3 (1,2) (2,1) (3,2)

 

In this transition table some reactions of signals on certain signal transmitting elements are deterministic, and others are non-deterministic. In the example above, if signal "1" reaches the node with the transmitting element "b", the reaction is non-deterministic.

Now your task is to write a program to judge if a tree structure with certain signal transmitting substance is valid.


Input

The input file contains several cases. Each case describes a sequence of NTA descriptions and some initial tree configurations. The first line for each case consists of three integers n, m and k. The integer n is the number of signals, m indicates the number of accepting signals, and k is number of signal transmitting elements. The following n k lines describe the transition table in row-major order. Each transition of a signal on signal transmitting element is given on a separate line. On such line every two numbers represent a possible transition.

This is followed by the description of tree structures. For every tree structure a number L is given on a separate line to indicate the level of the tree. The following L+1 lines containing a sequence of letters describe the tree structure. Each level is described in one line. There exist one space between two successive letters. The 0-th level begins firstly. In the tree structure, the empty nodes are marked by "*". The tree structure with L=-1 terminates the configurations of tree structures for that NTA, and this structure should not be judged.

The input is terminated by a description starting with n=0, m=0 and k=0. This description should not be processed.

Note: In each case, NTA will have at most l5 signals and 10 characters. The level of each tree will be no more than 10.


Output


For each NTA description, print the number of the NTA (NTAl, NTA2, etc.) followed by a colon. Then for each initial tree configuration of the NTA print the word "Valid" or "Invalid".

Print a blank line between NTA cases.


Sample Input

4 2 3
1 2
2 1
1 0
2 2
0 2 1 0
3 2
2 2
2 3
1 2
1 2
2 1
3 2
3
a
b c
a b c b
b a b a c a * *
2
b
a b
b c * *
-1
0 0 0


Output for the Sample Input

NTA1:
Valid
Invalid

ANSWER

 /* 解题思路
   倒推。从树叶开始,找出每个树叶有哪些accepting signal pair,然后分析出哪些
   signal pairs进入其父节点可以得到这些accepting signal pair。而进入上一级的节点的
   signal pair则必须能使树叶产生这些accepting signal pair。对于更上层的节点,要使下
   一级的节点得到其所需要的signal pair。
*/

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <iterator>
#include <sstream>
#include <complex>
#include <algorithm>

using namespace std;

typedef vector< pair<int, int> >    SignalPairs;

class TransitionTable
{
public:
    int n_;                     // number of signals
    int m_;                     // number of accepting signals
    int k_;                     // number of signal transmitting elements
    SignalPairs table_[15][10]; // describes a transition table

    TransitionTable() : n_(0), m_(0), k_(0)
    {
        cin >> n_ >> m_ >> k_;
        //cout << n_ << " " << m_ << " " << k_ << endl;
    }

    void InitTransitionTable()
    {
        string sLine;
        getline(cin, sLine, '\n');

        for (int i = 0; i < n_; ++i)
        {
            for (int j = 0; j < k_; ++j)
            {
                getline(cin, sLine, '\n');
                stringstream ss(sLine);
                istream_iterator<string> ibeg(ss), iend;
                vector<string> signals(ibeg, iend);
               
                for (int k = 0; k < (int)signals.size(); k += 2)
                {
                    pair<int, int> signal_pair(atoi(signals[k].c_str()), atoi(signals[k+1].c_str()));
                    table_[i][j].push_back(signal_pair);
                    //cout << signals[k] << " " << signals[k+1] << " ";
                }
                //cout << endl;
            }
        }
    }
};

class Node
{
public:
    char character_;
    vector<int> good_signals_;
    Node* lchild_;
    Node* rchild_;

    Node() : lchild_(NULL), rchild_(NULL)
    {
    }

    int FindGoodSignals(TransitionTable& table)
    {
        int nCharacter = character_ - 'a';
        int nAccepting = table.n_ - table.m_;
        good_signals_.clear();

        if ((NULL == lchild_ && NULL == rchild_) || ('*' == lchild_->character_ && '*' == rchild_->character_))
        {
            for (int i = 0; i < table.n_; ++i)
            {
                for (SignalPairs::const_iterator iter = table.table_[i][nCharacter].begin();
                    iter != table.table_[i][nCharacter].end(); ++iter)
                {
                    if (iter->first >= nAccepting && iter->second >= nAccepting)
                    {
                        good_signals_.push_back(i);
                        break;
                    }
                }
            }
        }
        else
        {
            for (int i = 0; i < table.n_; ++i)
            {
                for (SignalPairs::const_iterator iter = table.table_[i][nCharacter].begin();
                    iter != table.table_[i][nCharacter].end(); ++iter)
                {
                    if (find(lchild_->good_signals_.begin(), lchild_->good_signals_.end(), iter->first) !=
                        lchild_->good_signals_.end()
                        &&
                        find(rchild_->good_signals_.begin(), rchild_->good_signals_.end(), iter->second) !=
                        rchild_->good_signals_.end())
                    {
                        good_signals_.push_back(i);
                        break;
                    }
                }
            }
        }

        return (int)good_signals_.size();
    }
};

class Tree
{
public:
    int L_;
    Node *nodes_;

    Tree() : L_(0), nodes_(NULL)
    {
        cin >> L_;
        //cout << L_ << endl;
    }

    ~Tree()
    {
        if (L_ > 0)
        {
            delete[] nodes_;
            nodes_ = NULL;
        }
    }

    void InitTree()
    {
        int nNodeNum = (int)pow(2.0, L_+1)-1;
        int nLeaves = (int)pow(2.0, L_)-1;
        nodes_ = new Node[nNodeNum];

        for (int i = 0; i < nNodeNum; ++i)
        {
            cin >> (nodes_+i)->character_;
            //cout << (nodes_+i)->character_ << " ";

            if (i < nLeaves)
            {
                (nodes_+i)->lchild_ = (nodes_+2*i+1);
                (nodes_+i)->rchild_ = (nodes_+2*i+2);
            }
            else
            {
                (nodes_+i)->lchild_ = NULL;
                (nodes_+i)->rchild_ = NULL;
            }
        }
        //cout << endl;
    }

    bool IsValidNTA(TransitionTable& table)
    {
        int nNodeNum = (int)pow(2.0, L_+1)-1;
       
        for (int i = nNodeNum - 1; i >= 0; --i)
        {
            if ('*' != (nodes_+i)->character_ && ((nodes_+i)->FindGoodSignals(table) <= 0))
                return false;
        }

        if (find(nodes_->good_signals_.begin(), nodes_->good_signals_.end(), 0) != nodes_->good_signals_.end())
            return true;

        return false;
    }
};

int main()
{
#ifndef ONLINE_JUDGE
    freopen("1011.in", "r", stdin);
#endif

    int count = 0;

    while (true)
    {
        TransitionTable table;
        if (0 == table.n_ || 0 == table.m_ || 0 == table.k_)
            break;

        table.InitTransitionTable();

        if (count > 0)
            cout << endl;
        cout << "NTA" << ++count << ":\n";

        while (true)
        {
            Tree tree;
            if (-1 == tree.L_)
                break;

            tree.InitTree();
            if (tree.IsValidNTA(table))
                cout << "Valid\n";
            else
                cout << "Invalid\n";
        }
    }

    return 0;
}

May 16

君要臣下,臣可以不下(转贴)

《南方人物周刊》专栏
----------------
 
 
众所周知,自从70年代尼克松的水门丑闻曝光以来,美国政治生活中的重大丑闻都会被加上“门”字,比如伊朗门、白水门、卡特里娜门、佛利门、普莱姆门……等等。最近,在这个长长的“门”名单里,又多了一“扇”门:律师门。
 
事情是这样的:200612月,美国司法部在白宫的批准下,突然以“工作表现不佳”为由,解雇了8名联邦律师(更确切地说是联邦检察官)。这些律师在震惊愤慨之余,将这事捅到了媒体。从20071月份开始,各大媒体开始积极报道这件事,讨论这次解雇是否合理。3月份,国会司法委员会开始调查此事,传唤司法部的相关当事人。在媒体和国会越来越气势汹汹的声讨下,司法部策划此次解雇事件的司法部长助理桑普森被迫辞职,同时,要求司法部长冈泽尔辞职的呼声也越来越高。目前,虽然此事仍然在调查当中,但是“律师门”的说法已经遍及媒体了。
 
大家可能会奇怪,司法部解雇自己的雇员,怎么会成为丑闻呢?众所周知,在美国的“分立三权”中,联邦行政权这一块是完全由总统统领的,也就是说,“各部委”负责人是总统任命的,不是民选职位。同理,各“部委”内部的工作人员也是由其负责人任免,也不是民选职位。这也是为什么美国每一个新总统就职,都会带来一次“领导班子”的大更迭:每个总统都会想办法在政府内部安插本党的、甚至本人的亲信,以提高本届政府的行政效率。就司法部来说,里根就任的前两年里,93个联邦律师里有89个被替换,克林顿政府也是93个里面替换了89个。布什政府最初两年里,也替换了88个联邦律师。虽然这个替换有一个参议院批准的程序,但是这个审批针对的这些职位的“任命”,而不是“罢免”――这一点,在1926年的“麦尔斯对美国”的最高法院判例中已经做出明确澄清。在这种情况下,冈泽尔解雇本部的9个律师,怎么会酿成政治风暴呢?
 
问题在于,很多议员、媒体以及这8个律师本人认为,此次大规模解雇不是因为什么“工作表现”――因为在解雇之前,根本就不存在一个“工作表现”的评审程序,而是因为这些律师对布什政府“效忠不够”。甚至有人认为这次解雇是对这些律师的打击报复――报复他们作为检察官起诉民主党员不力,或者起诉共和党成员太卖力。
 
比如其中最有争议的人物南加州律师莱姆。05年她曾经积极调查美国近年来最大的腐败案克宁汉姆案,并成功了起诉了共和党议员克宁汉姆。20065月,她又将调查之手伸向了国会拨款委员会主席共和党员利维斯。同时,由克宁汉姆案顺藤摸瓜,她又开始调查CIA前高官佛格。就在被解雇的前夕,她还在忙于起诉佛格。莱姆在这个关头被解雇,难怪有人认为这是共和党内部的“清洗运动”,与“工作表现”没有关系。当然司法部还是辩称,这个解雇并不是阻止莱姆起诉佛格,因为就在莱姆被解雇之后,司法部还是起诉了佛格。
 
其他7个联邦律师的解雇,或多或少也存在这样的争议。比如,新墨西哥州的联邦律师依格里塞斯表示,他之所以被解雇,是因为他没有加快调查民主党人的投票舞弊案;内华达的波根,在被解雇前正在调查内华达共和党州长吉本斯;而东阿肯色州联邦律师克明斯的解雇,据说唯一的理由就是布什总统的顾问洛伍想让他腾出位子,安插他的一个亲信。
 
鉴于这些争议的存在,双方掐作一团。冈泽尔坚称这次解雇没有任何报复的企图,纯粹基于“工作表现”,而“工作表现是广义而言的,包括政策优先性的安排等等”,同时强调司法部作为行政部门,其职能本来就是“为总统服务”,具有政党性的特点。而批评者则认为,政党利益不应当高于国家利益,虽然联邦律师由政府任命,一旦开始工作,他们应当秉承中立原则。
 
应当说,在这场辩论中,冈泽尔节节败退。助手辞职,国会听证,本人被传唤,职位岌岌可危。尤其在他声称他并没有深入参与这件事、但是他的助手在国会听证中否证了这一说法之后,连共和党的很多人都开始跟他划清界限。布什虽然原则上表示支持他,也一再要求他“做出更好的解释”。事实上,布什在这场政治斗争中自身难保:他的几个“爱卿”洛伍、玛尔斯等都受到牵连,被国会传唤。
 
堂堂司法部长,有总统这个靠山,有“麦尔斯对美国”这个判例的前科,有政府大规模更新联邦律师的惯例,竟然不能扳倒几个手下的律师,甚至可能被他们扳倒,可见在美国,即使是部长,权力也非常有限。政党利益不能高于国家利益,政见不能超越公益,是这场斗争成为丑闻的根本原因。
 
事实上,稍微熟悉一点美国宪政史的人就知道,这场斗争其实并没有什么新意。早在1867年,也就是中国人见到皇帝还在战战兢兢地下跪的年代,美国就有一位总统因为试图炒一个官员的鱿鱼而差点被国会炒了鱿鱼。那就是美国第一个遭受弹劾的总统安朱·约翰逊。当时约翰逊因为与其战争部长斯坦顿政见不合,试图解雇他。国会声称该解雇违反了当时的“职位期满法案”,对约翰逊启动了弹劾程序。众议院都已经通过了弹劾总统,幸亏参议院以一票之差将约翰逊从“下岗”的边缘给救了回来。
 
之后总统的官员任免权问题就一直反反复复。1926年的“麦尔斯对美国”判例中(当时总统威尔逊要解雇一个邮政官员麦尔斯),最高法院判决“职位期满法案”违宪,也就是说,总统有解雇其内阁成员的自由。但是也有法学家说,这并不意味着总统可以随心所欲地解雇官员,因为当人员任免影响了政府为公众提供有效服务时,这本身又是违宪的。1935年,当罗斯福因为联邦交易委员会主席汉弗瑞不支持新政而解雇他时,汉弗瑞则又把罗斯福政府给告了。最后法院裁决,由于联邦交易委员会不仅仅涉及行政权力,而且涉及部分的司法权,政府不能自由解雇其官员,罗斯福政府败诉。
 
看来,据我有限的知识,从1867年以来,美国就有三起由官员任免引起的“君臣冲突”。目前这个律师门事件,则很可能成为第四起这样的案例。相比专权国家里“君主”可以威风凛凛地大笔一挥就抹去无数下属的政治生命甚至肉体生命的“潇洒”,在一个三权分立的国家里做一个“君主”,是多么窝囊的一件事:君要臣下,臣就是不下。不但“臣”不下,而且“臣”还可以要“君”下。可见,在这样的国家里,真正的“君主”不是某一个人,而是在各种力量相互制衡不断被激活的宪法。
April 12

10年编程无师自通 (转贴)

最近看到的一篇很好的文章,英文原文出自 http://norvig.com/21-days.html
 
 
出处:Teach Yourself Programming in Ten Years ,Peter Norvig 郭晓刚翻译
 
为什么每个人都急不可耐? 
走进任何一家书店,你会看见《Teach Yourself Java in 7 Days》(7天Java无师自通)的旁边是一长排看不到尽头的类似书籍,它们要教会你Visual Basic、Windows、Internet等等,而只需要几天甚至几小时。我在Amazon.com上进行了如下搜索: 
    pubdate: after 1992 and title: days and (title: learn or title: teach yourself) 
    (出版日期:1992年后 and 书名:天 and (书名:学会 or 书名:无师自通)) 
我一共得到了248个搜索结果。前面的78个是
计算机书籍(第79个是《Learn Bengali in 30 days》,30天学会孟加拉语)。我把关键词“days”换成“hours”,得到了非常相似的结果:这次有253本书,头77本是计算机书籍,第78 本是《Teach Yourself Grammar and Style in 24 Hours》(24小时学会文法和文体)。头200本书中,有96%是计算机书籍。 
结论是,要么是人们非常急于学会计算机,要么就是不知道为什么计算机惊人地简单,比任何东西都容易学会。没有一本书是要在几天里教会人们贝多芬或者量子物理学,甚至怎样帮狗打扮。 
让我们来分析一下像《Learn Pascal in Three Days》(3天学会Pascal)这样的题目到底是什么意思: 
 
学会:在3天时间里,你不够时间写一些有意义的程序,并从它们的失败与成功中学习。你不够时间跟一些有经验的程序员一起工作,你不会知道在那样的环境中是什么滋味。简而言之,没有足够的时间让你学到很多东西。所以这些书谈论的只是表面上的精通,而非深入的理解。如Alexander Pope(英国诗人、作家,1688-1744)所言,一知半解是危险的(a little learning is a dangerous thing) 
Pascal:在3天时间里你可以学会Pascal的语法(如果你已经会一门类似的语言),但你无法学到多少如何运用这些语法。简而言之,如果你是,比如说一个Basic程序员,你可以学会用Pascal语法写出Basic风格的程序,但你学不到Pascal真正的优点(和缺点)。那关键在哪里? Alan Perlis(ACM第一任主席,图灵奖得主,1922-1990)曾经说过:“如果一门语言不能影响你对编程的想法,那它就不值得去学”。另一种观点是,有时候你不得不学一点Pascal(更可能是Visual Basic和javascript之类)的皮毛,因为你需要接触现有的工具,用来完成特定的任务。但此时你不是在学习如何编程,你是在学习如何完成任务。 
3天:不幸的是,这是不够的,正如下一节所言。 
 
 
10年编程无师自通 
一些研究者(Hayes、Bloom)的研究表明,在许多领域,都需要大约10 年时间才能培养出专业技能,包括国际象棋、作曲、绘画、钢琴、游泳、网球,以及神经心理学和拓扑学的研究。似乎并不存在真正的捷径:即使是莫扎特,他4 岁就显露出音乐天才,在他写出世界级的音乐之前仍然用了超过13年时间。再看另一种音乐类型的披头士,他们似乎是在1964年的Ed Sullivan节目中突然冒头的。但其实他们从1957年就开始表演了,即使他们很早就显示出了巨大的吸引力,他们第一次真正的成功——Sgt. Peppers——也要到1967年才发行。Samuel Johnson(英国诗人)认为10 年还是不够的:“任何领域的卓越成就都只能通过一生的努力来获得;稍低一点的代价也换不来。”(Excellence in any department can be attained only by the labor of a lifetime; it is not to be purchased at a lesser price.)乔叟(Chaucer,英国诗人,1340-1400)也抱怨说:“生命如此短暂,掌握技艺却要如此长久。”(the lyf so short, the craft so long to lerne.) 
下面是我在编程这个行当里获得成功的处方: 
 
对编程感兴趣,因为乐趣而去编程。确定始终都能保持足够的乐趣,以致你能够将10年时间投入其中。 
跟其他程序员交谈;阅读其他程序。这比任何书籍或训练课程都更重要。 
编程。最好的学习是从实践中学习。用更加技术性的语言来讲,“个体在特定领域最高水平的表现不是作为长期的经验的结果而自动获得的,但即使是非常富有经验的个体也可以通过刻意的努力而提高其表现水平。”(p. 366),而且“最有效的学习要求为特定个体制定适当难度的任务,有意义的反馈,以及重复及改正错误的机会。”(p. 20-21)《Cognition in Practice: Mind, Mathematics, and Culture in Everyday Life》(在实践中认知:心智、数学和日常生活的文化)是关于这个观点的一本有趣的参考书。 
如果你愿意,在大学里花上4年时间(或者再花几年读研究生)。这能让你获得一些工作的入门资格,还能让你对此领域有更深入的理解,但如果你不喜欢进学校,(作出一点牺牲)你在工作中也同样能获得类似的经验。在任何情况下,单从书本上学习都是不够的。“计算机科学的教育不会让任何人成为内行的程序员,正如研究画笔和颜料不会让任何人成为内行的画家”, Eric Raymond,《The New Hackers Dictionary》(新黑客字典)的作者如是说。我曾经雇用过的最优秀的程序员之一仅有高中学历;但他创造出了许多伟大的软件,甚至有讨论他本人的新闻组,而且股票期权让他达到我无法企及的富有程度(译注:指Jamie Zawinski,Xemacs和Netscape的作者)。 
跟别的程序员一起完成项目。在一些项目中成为最好的程序员;在其他一些项目中当最差的一个。当你是最好的程序员时,你要测试自己领导项目的能力,并通过你的洞见鼓舞其他人。当你是最差的时候,你学习高手们在做些什么,以及他们不喜欢做什么(因为他们让你帮他们做那些事)。 
接手别的程序员完成项目。用心理解别人编写的程序。看看在没有最初的程序员在场的时候理解和修改程序需要些什么。想一想怎样设计你的程序才能让别人接手维护你的程序时更容易一些。 
学会至少半打编程语言。包括一门支持类抽象(class abstraction)的语言(如Java或C++),一门支持函数抽象(functional abstraction)的语言(如Lisp或ML),一门支持句法抽象(syntactic abstraction)的语言(如Lisp),一门支持说明性规约(declarative specification)的语言(如Prolog或C++模版),一门支持协程(coroutine)的语言(如Icon或Scheme),以及一门支持并行处理(parallelism)的语言(如Sisal)。 
记住在“计算机科学”这个词组里包含“计算机”这个词。了解你的计算机执行一条指令要多长时间,从内存中取一个word要多长时间(包括缓存命中和未命中的情况),从磁盘上读取连续的数据要多长时间,定位到磁盘上的新位置又要多长时间。(答案在这里。) 
尝试参与到一项语言标准化工作中。可以是ANSI C++委员会,也可以是决定自己团队的编码风格到底采用2个空格的缩进还是4个。不论是哪一种,你都可以学到在这门语言中到底人们喜欢些什么,他们有多喜欢,甚至有可能稍微了解为什么他们会有这样的感觉。 
拥有尽快从语言标准化工作中抽身的良好判断力。 

抱着这些想法,我很怀疑从书上到底能学到多少东西。在我第一个孩子出生前,我读完了所有“怎样……”的书,却仍然感到自己是个茫无头绪的新手。 30个月后,我第二个孩子出生的时候,我重新拿起那些书来复习了吗?不。相反,我依靠我自己的经验,结果比专家写的几千页东西更有用更靠得住。 
Fred Brooks在他的短文《No Silver Bullets》(没有银弹)中确立了如何发现杰出的软件设计者的三步规划: 
 
尽早系统地识别出最好的设计者群体。 
指派一个事业上的导师负责有潜质的对象的发展,小心地帮他保持职业生涯的履历。 
让成长中的设计师们有机会互相影响,互相激励。 

这实际上是假定了有些人本身就具有成为杰出设计师的必要潜质;要做的只是引导他们前进。Alan Perlis说得更简洁:“每个人都可以被教授如何雕塑;而对米开朗基罗来说,能教给他的倒是怎样能够不去雕塑。杰出的程序员也一样”。 
所以尽管去买那些Java书;你很可能会从中找到些用处。但你的生活,或者你作为程序员的真正的专业技术,并不会因此在24小时、24天甚至24个月内发生真正的变化。 
 
参考文献 
Bloom, Benjamin (ed.) Developing Talent in Young People, Ballantine, 1985. 
Brooks, Fred, No Silver Bullets, IEEE Computer, vol. 20, no. 4, 1987, p. 10-19. 
Hayes, John R., Complete Problem Solver, Lawrence Erlbaum, 1989. 
Lave, Jean, Cognition in Practice: Mind, Mathematics, and Culture in Everyday Life, Cambridge University Press, 1988. 
 
答案 
各种操作的计时,2001年夏天在一台典型的1GHz PC上完成: 
    执行单条指令             1 纳秒 = (1/1,000,000,000) 秒 
    从L1缓存中取一个word        2 纳秒 
    从主内存中取一个word        10 纳秒 
    从连续的磁盘位置中取一个word    200 纳秒 
    从新的磁盘位置中取一个word(寻址) 8,000,000纳秒 = 8毫秒
April 01

终于完成了报税

这周末算是完成了一件大事,也没空去写程序了。第一次报税,太辛苦,一连研究了ufile,quicktax,studiotax三个软件,最后用studiotax完成,因为是免费的。但是studiotax的功能还比较弱,很多都需要在表格上手动完成。
March 25

Area (1010)

Jerry, a middle school student, addicts himself to mathematical research. Maybe the problems he has thought are really too easy to an expert. But as an amateur, especially as a 15-year-old boy, he had done very well. He is so rolling in thinking the mathematical problem that he is easily to try to solve every problem he met in a mathematical way. One day, he found a piece of paper on the desk. His younger sister, Mary, a four-year-old girl, had drawn some lines. But those lines formed a special kind of concave polygon by accident as Fig. 1 shows.


Fig. 1 The lines his sister had drawn

"Great!" he thought, "The polygon seems so regular. I had just learned how to calculate the area of triangle, rectangle and circle. I'm sure I can find out how to calculate the area of this figure." And so he did. First of all, he marked the vertexes in the polygon with their coordinates as Fig. 2 shows. And then he found the result--0.75 effortless.


Fig.2 The polygon with the coordinates of vertexes

Of course, he was not satisfied with the solution of such an easy problem. "Mmm, if there's a random polygon on the paper, then how can I calculate the area?" he asked himself. Till then, he hadn't found out the general rules on calculating the area of a random polygon. He clearly knew that the answer to this question is out of his competence. So he asked you, an erudite expert, to offer him help. The kind behavior would be highly appreciated by him.


Input

The input data consists of several figures. The first line of the input for each figure contains a single integer n, the number of vertexes in the figure. (0 <= n <= 1000).

In the following n lines, each contain a pair of real numbers, which describes the coordinates of the vertexes, (xi, yi). The figure in each test case starts from the first vertex to the second one, then from the second to the third, …… and so on. At last, it closes from the nth vertex to the first one.

The input ends with an empty figure (n = 0). And this figure not be processed.


Output

As shown below, the output of each figure should contain the figure number and a colon followed by the area of the figure or the string "Impossible".

If the figure is a polygon, compute its area (accurate to two fractional digits). According to the input vertexes, if they cannot form a polygon (that is, one line intersects with another which shouldn't be adjoined with it, for example, in a figure with four lines, the first line intersects with the third one), just display "Impossible", indicating the figure can't be a polygon. If the amount of the vertexes is not enough to form a closed polygon, the output message should be "Impossible" either.

Print a blank line between each test cases.


Sample Input

5
0 0
0 1
0.5 0.5
1 1
1 0
4
0 0
0 1
1 0
1 1
0

Answer
 
#include <iostream>
#include <cmath>
#include <vector>
using namespace std;
#define MIN(x, y)       ((x < y) ? x : y)
#define MAX(x, y)       ((x > y) ? x : y)
class CPoint
{
public:
    CPoint(int x0 = 0, int y0 = 0)
        : x(x0)
        , y(y0)
    {
    }
    bool operator ==(const CPoint& p)
    {
        return (x == p.x && y == p.y);
    }
// Attributes: 
    double x, y;
};
class CPolygon
{
public:
    CPolygon()
    {
        vertexes.clear();
    }
    ~CPolygon()
    {
        vertexes.clear();
    }
    void AddVertex(const CPoint& point)
    {
        vertexes.push_back(point);
    }
    // 向量X乘,p1视为原点
    // 返回结果大于0,则在p0在p1p2的顺时针方向,小于则在逆时针方向,等于0两向量共线
    double Multiply(const CPoint& p0, const CPoint& p1, const CPoint& p2) const
    {
        double x1 = p2.x - p1.x;
        double y1 = p2.y - p1.y;
        double x2 = p0.x - p1.x;
        double y2 = p0.y - p1.y;
        return x1 * y2 - x2 * y1;
    }
   
    bool IsIntersectant(const CPoint& p1, const CPoint& p2, const CPoint& q1, const CPoint& q2) const
    {
        // 判断两线段是否相交:
        // 我们分两步确定两条线段是否相交:
        // (1)快速排斥试验
        //     设以线段 P1P2 为对角线的矩形为R, 设以线段 Q1Q2 为对角线的矩形为T,
        //     如果R和T不相交,显然两线段不会相交。
        if (MAX(p1.x, p2.x) < MIN(q1.x, q2.x) || MAX(q1.x, q2.x) < MIN(p1.x, p2.x) ||
            MAX(p1.y, p2.y) < MIN(q1.x, q2.x) || MAX(q1.y, q2.y) < MIN(p1.y, p2.y))
            return false;
        // (2)跨立试验
        //     如果两线段相交,则两线段必然相互跨立对方。若P1P2跨立Q1Q2 ,则矢量
        //     ( P1 - Q1 ) 和( P2 - Q1 )位于矢量( Q2 - Q1 ) 的两侧,即
        //     ( P1 - Q1 ) × ( Q2 - Q1 ) * ( P2 - Q1 ) × ( Q2 - Q1 ) < 0。
        //     上式可改写成( P1 - Q1 ) × ( Q2 - Q1 ) * ( Q2 - Q1 ) × ( P2 - Q1 ) > 0。
        //     当 ( P1 - Q1 ) × ( Q2 - Q1 ) = 0 时,说明 ( P1 - Q1 ) 和 ( Q2 - Q1 )共线,
        //     但是因为已经通过快速排斥试验,所以 P1 一定在线段 Q1Q2上;同理,
        //     ( Q2 - Q1 ) ×(P2 - Q1 ) = 0 说明 P2 一定在线段 Q1Q2上。所以判断P1P2跨立Q1Q2
        //     的依据是:( P1 - Q1 ) × ( Q2 - Q1 ) * ( Q2 - Q1 ) × ( P2 - Q1 ) >= 0。
        //     同理判断Q1Q2跨立P1P2的依据是:
        //     ( Q1 - P1 ) × ( P2 - P1 ) * ( P2 - P1 ) × ( Q2 - P1 ) >= 0。
       
        if (Multiply(p1, q1, q2) * Multiply(p2, q1, q2) > 0.0 ||
            Multiply(q1, p1, p2) * Multiply(q2, p1, p2) > 0.0)
            return false;
        return true;
    }
    bool IsPolygon()
    {
        int n = (int)vertexes.size();
        // 形成多边形的最少顶点数
        if (vertexes.size() < 3)
            return false;
        for (int i = 0; i < n; ++i)
        {
            int k = (i + 1 < n)? i + 1 : 0;
            for (int j = (k == 0); j < i - 1; ++j)
            {
                if (IsIntersectant(vertexes[i], vertexes[k], vertexes[j], vertexes[j + 1]))
                    return false;
            }
        }
        return true;
    }
    double CalculateArea() const
    {
        double s = 0.0;
        int i;
       
        // 多边形面积公式
        for(i = 0; i < (int)vertexes.size() - 1; ++i)
            s += (vertexes[i].x * vertexes[i+1].y - vertexes[i].y * vertexes[i+1].x);
        s += (vertexes[i].x * vertexes[0].y - vertexes[i].y * vertexes[0].x);
        s = fabs(s) / 2.0;
        return s;
    }
   
// Attributes:
    vector<CPoint> vertexes;
};
int main()
{
#ifndef ONLINE_JUDGE
    freopen("1010.in", "r", stdin);
#endif
   
    for (int figure = 1; ; figure++)
    {  
        int n;
        cin >> n;
        if (n <= 0)
            break;
        cout << "Figure " << figure << ": ";
        CPolygon polygon;
        for (int i = 0; i < n; i++)
        {
            CPoint vertex;
            cin >> vertex.x >> vertex.y;
            polygon.AddVertex(vertex);
        }
       
        if (polygon.IsPolygon())
        {
            double s = polygon.CalculateArea();
            if (s > 1e-6)
            {
                cout.precision(2);
                cout.setf(ios::fixed);
                cout << s << endl << endl;
                continue;
            }
        }
        cout << "Impossible\n\n";
    }
    return 0;
}

March 17

Enigma (1009)

In World War II, Germany once used an electronic encryption machine called Enigma, which played a decisive role in the initial victories of Nazi Germany. It was proved to be one of the most reliable encryption systems in history. However, it was the blind trust on the reliability of the machine that brought about the doom of its user.

The structure of a one-rotor Enigma is shown as follows (the Enigma has only six keys):

The key element of the Enigma is the rotor, as shown in the second figure, which uses electronic circuits to transform plaintext (input from keyboard) into cryptograph (output on screen). When one key on the keyboard is pressed, the corresponding cryptograph is shown on screen. Then the rotor will automatically revolve a one-letter-step to a different position. The following figures illustrate how the rotor works when letter "b" is pressed three successively times:

When letter "b" is pressed for the first time, the signal goes through the circuit and "A" is shown on screen. When the key is released, the rotor revolves one-letter-step to a different position that changes all the corresponding circuits so that each letter now has a different cryptograph. When letter "b" is pressed for the second time, the corresponding cryptograph is "C". So when letter "b" is pressed for the third time, the cryptograph is "E" according to the principle specified above.

Now the following figure shows the structure of a two-rotor Enigma.

The difference is that when a key is released, the second rotor won't revolve a step until the first one has finished one circle and returns to the original position. This is also the same in the case of three-rotor Enigma. That is: Only after the first rotor has finished one circle and return to the initial status, the second rotor will revolve a step. And only after the second rotor has finish one circle, the third rotor will revolve a step.

However, how did the Allied Forces obtain the information encrypted by Enigma? A person named Hans-Thilo Schimdt was very essential. He acted as a spy and provided the initial status of the three rotors in each Enigma to the Allied Forces once a month. The Allied Forces thus got everything they wanted by deciphering the intercepted cryptograph using the information offered by the spy.

Now, please design a program to obtain the plaintexts using the information offered by the Allied Forces.


Input

The input file contains several test cases representing several three-rotor Enigmas. The last test case in the input file is followed by a line containing a number 0.

Each case begins with a line containing an integer m (1 <= m <= 26) which indicates the number of sequential letters each rotor has. The first letter will always be A. (for example, m = 6 tells each rotor has 6 keys from A to F). The following three lines describe the initial status of the three rotors respectively. Each of them contains a string consisting of m capital character. For instance, a rotor with the initial status "BADFEC" indicates that the initial encrypt mechanism is to convert "abcdef" to "BADFEC", that is, original letter "a" corresponding to cryptograph letter "B", "b" to "A", "c" to "D", "d" to "F", "e" to "E" and "f" to "C". The forth line of each case contains an integer n which tells the number of cryptographs generated by the above Enigma. Then the following n lines are the n cryptographs respectively, which consist of m capital characters each.


Output


For each test case, the output should consist of two parts. The first line is the number of Enigma and a colon. The following lines are the plaintexts deciphered from the corresponding cryptographs. Each plaintext should be printed in one line. Note: The characters in the plaintext should be converted to the corresponding lowercases before they are printed.

Insert a blank line between test cases.


Sample Input

6
BADFEC
ABCDEF
ABCDEF
1
ACE
0


Output for the Sample Input

Enigma 1:
bbb

Answer
 
#include <iostream>
#include <string>
using namespace std;
//  旋转rotor的方法
//      rotor       需要进行旋转操作的rotor
//      m           rotor所包含元素的数量
//
void Revolve(int rotor[], int m)
{
    // 纪录rotor的最后一个元素
    int temp = rotor[m - 1];
   
    // 将所有元素向后移动
    for (int i = m - 1; i > 0; i--)
        rotor[i] = rotor[i - 1];
       
    // 用最后一个元素替换第一个元素以完成旋转
    rotor[0] = temp;
}
int main()
{
#ifndef ONLINE_JUDGE
    freopen("1009.in", "r", stdin);
#endif
   
    // 读入需要破解的第一个Engima的长度
    int m;
    cin >> m;
   
    for (int enigma = 1; ; enigma++)
    {
        cout << "Enigma " << enigma << ":" << endl;
       
        // 纪录rotor的字符串
        string sr[3];
       
        // 读入rotor字符串
        for (int i = 0; i < 3; i++)
        {
            cin >> sr[i];
            //cout << sr[i] << endl;
        }
       
        // 读入该enigma需要破解的密文行数
        int n;
        cin >> n;
       
        // 循环逐行破解密文
        for (int i = 0; i < n; i++)
        {
            // 纪录rotor的对应关系
            int rotor[3][26];
           
            // 得到原文和密文的对应关系,每破解完一行密文,要重新得到对应关系
            for (int j = 0; j < m; j++)
            {
                for (int k = 0; k < 3; k++)
                {
                    rotor[k][j] = sr[k][j] - 'A' - j;
                   
                    if (rotor[k][j] < 0)
                        rotor[k][j] += m;
                }
            }
           
            // 读入密文
            string cryptographs;
            cin >> cryptographs;
            //cout << cryptographs << endl;
           
            // 旋转计数器
            int counter = 0;
            // 记录密文长度
            int len = cryptographs.length();
           
            // 循环进行解密
            for (int j = 0; j < len; j++)
            {
                // 密文字符对应的数值,A = 0, B = 1 ...
                int u = cryptographs[j] - 'A';
               
                // 循环进行3个rotor的破解
                for (int k = 2; k >= 0; k--)
                {
                    // 寻找密文对应原文的那一个字符
                    for (int l = 0; l < m; l++)
                    {
                        // 推理规律是原文字符加上变换关系应该等于密文字符
                        if (u  == (l + rotor[k][l]) % m)
                        {
                            u = l;
                            break;
                        }
                    }
                }
               
                // 将得到的原文字符转还为小写字母
                cryptographs[j] = (char)(u + 'a');
                // 旋转计数累加
                counter++;
                // 旋转一次rotor0
                Revolve(rotor[0], m);
               
                // 如果rotor0旋转了一整圈,则rotor1进行旋转
                if (counter % m == 0)
                    Revolve(rotor[1], m);
               
                // 如果rotor1也旋转了一整圈,则rotor2进行旋转
                if (counter % (m * m) == 0)
                    Revolve(rotor[2], m);
            }
           
            // 输出原文
            cout << cryptographs << endl;
        }
       
        // 读入下一组Enigma的长度,如果等于零则结束
        cin >> m;
        if (m > 0)
            cout << endl;
        else
            break;
    }
}
March 10

Gnome Tetravex (1008)

Hart is engaged in playing an interesting game, Gnome Tetravex, these days. In the game, at the beginning, the player is given n*n squares. Each square is divided into four triangles marked four numbers (range from 0 to 9). In a square, the triangles are the left triangle, the top triangle, the right triangle and the bottom triangle. For example, Fig. 1 shows the initial state of 2*2 squares.


Fig. 1 The initial state with 2*2 squares

The player is required to move the squares to the termination state. In the termination state, any two adjoining squares should make the adjacent triangle marked with the same number. Fig. 2 shows one of the termination states of the above example.


Fig. 2 One termination state of the above example

It seems the game is not so hard. But indeed, Hart is not accomplished in the game. He can finish the easiest game successfully. When facing with a more complex game, he can find no way out.

One day, when Hart was playing a very complex game, he cried out, "The computer is making a goose of me. It's impossible to solve it." To such a poor player, the best way to help him is to tell him whether the game could be solved. If he is told the game is unsolvable, he needn't waste so much time on it.


Input

The input file consists of several game cases. The first line of each game case contains one integer n, 0 <= n <= 5, indicating the size of the game.

The following n*n lines describe the marking number of these triangles. Each line consists of four integers, which in order represent the top triangle, the right triangle, the bottom triangle and the left triangle of one square.

After the last game case, the integer 0 indicates the termination of the input data set.


Output

You should make the decision whether the game case could be solved. For each game case, print the game number, a colon, and a white space, then display your judgment. If the game is solvable, print the string "Possible". Otherwise, please print "Impossible" to indicate that there's no way to solve the problem.

Print a blank line between each game case.

Note: Any unwanted blank lines or white spaces are unacceptable.


Sample Input

2
5 9 1 4
4 4 5 6
6 8 5 4
0 4 4 3
2
1 1 1 1
2 2 2 2
3 3 3 3
4 4 4 4
0


Output for the Sample Input

Game 1: Possible

Game 2: Impossible

 
Answer
 
//  解题思路
//      读入每个方块上下左右的四个数字,并进行分类,对于各位置上数字相同的方块看做同
//  同一类方块,并统计各类方块的数量。回溯方块的排列方式,对方块按类进行回溯,而不是
//  在n*n个方块中回溯。
//
#include <iostream>
using namespace std;
 
typedef struct Square
{
    int top;
    int right;
    int bottom;
    int left;
} SQUARE;
 
//      n           方块的边长
//      square[]    各种方块的数字排列方式
//      types       不同类型方块的数量
//      number[]    每种类型方块的数量
//
bool Try(int n, SQUARE square[], int types, int number[])
{
    int f = 0;
    int state[25];
    state[0] = -1;
   
    // 回溯过程
    while (f >= 0)
    {
        if (state[f] < types - 1)
        {
            state[f]++;
           
            // 该类型方块是否已经用完
            if (number[state[f]] == 0)
                continue;
               
            // 如果不是第一列的方块,则比较右边的数字是否和前面方块左边的数字相同
            if (f % n > 0 && square[state[f - 1]].right != square[state[f]].left)
                continue;
           
            // 如果不是第一行的方块,则比较上面的数字是否和上面方块下面的数字相同
            if (f / n > 0 && square[state[f - n]].bottom != square[state[f]].top)
                continue;
           
            // 该类型方块减少一个
            number[state[f]]--;
               
            // 如果没有排满n*n个方块,继续排放下一个方块
            if (f < n * n - 1)
                state[++f] = -1;
            else
                return true;
        }
        else
        {
            number[state[--f]]++;
        }
    }
   
    return false;
}
 
int main()
{
#ifndef ONLINE_JUDGE
    freopen("1008.in", "r", stdin);
#endif
    int n;
    cin >> n;
   
    for (int gamecounter = 1; ; gamecounter++)
    {
        SQUARE square[25];
        int number[25];
        int types = 0;
           
        for (int i = 0; i < n * n; i++)
        {  
            // 逐个读入方块的数字
            int t, r, b, l;
            cin >> t >> r >> b >> l;
               
            // 对方块进行归类
            int j;
            for (j = 0; j < types; j++)
            {
                if (square[j].top == t && square[j].right == r && square[j].bottom == b && square[j].left == l)
                    break;
            }
           
            // 如果是已有的方块,只需将此类方块数量增加一个,
            // 如果是没出现过的方块则纪录这种新方块类型
            if (j < types)
                number[j]++;
            else
            {
                square[j].top = t;
                square[j].right = r;
                square[j].bottom = b;
                square[j].left = l;
                number[types++] = 1;
            }
        }
       
        // 输出回溯的结果
        if (Try(n, square, types, number))
            cout << "Game " << gamecounter << ": Possible" << endl;
        else
            cout << "Game " << gamecounter << ": Impossible" << endl;
       
        // 读入下一个要处理的方块边长
        cin >> n;
       
        // 如果读入0则退出循环
        if (n > 0)
            cout << endl;
        else
            break;
    }
}
March 07

Numerical Summation of a Series (1007)

Produce a table of the values of the series


Equation 1

for the 2001 values of x, x= 0.000, 0.001, 0.002, ..., 2.000. All entries of the table must have an absolute error less than 0.5e-12 (12 digits of precision). This problem is based on a problem from Hamming (1962), when mainframes were very slow by today's microcomputer standards.

Input

This problem has no input.

Output

The output is to be formatted as two columns with the values of x and y(x) printed as in the C printf or the Pascal writeln.

printf("%5.3f %16.12f\n", x, psix )  writeln(x:5:3, psix:16:12)

As an example, here are 4 acceptable lines out of 2001.

0.000   1.644934066848
...
0.500   1.227411277760
...
1.000   1.000000000000
...
2.000   0.750000000000

The values of x should start at 0.000 and increase by 0.001 until the line with x=2.000 is output.

Hint

The problem with summing the sequence in equation 1 is that too many terms may be required to complete the summation in the given time. Additionally, if enough terms were to be summed, roundoff would render any typical double precision computation useless for the desired precision.

To improve the convergence of the summation process note that

Equation 2

which implies y(1)=1.0. One can then produce a series for y(x) - y(1) which converges faster than the original series. This series not only converges much faster, it also reduces roundoff loss.

This process of finding a faster converging series may be repeated to produce sequences which converge more and more rapidly than the previous ones.

The following inequality is helpful in determining how may items are required in summing the series above.
Equation 3  

 
Answer
 
#define N       10000       /* number of terms to sum */
int main()
{
    double x;
    double sum;         /* used for computation summations */
    double psix, psix1; /* psix(x) and psix(1) */
    double dx;          /* value of the first accelerated series at x */
    double d2;          /* value of the first accelerated series at 2 */
    double ddx;         /* value of the second accelerates series at x */
    int i, k;
    double kd;
   
    psix1 = 1;
    d2 = 0.25;
   
    for (i = 0; i <= 2000 ; i++)
    {
        x = i*0.001;
        sum = 0.0;
               
        for (k = 1; k <= N; k++)
        {
            kd = k;
            sum += 1.0/(kd*(kd+1)*(kd+2)*(kd+x));
        }
       
        ddx = (2.0-x)*sum;
       
        /* get the value of the first accelerated series */
        dx = (1.0-x)*(d2+ddx);
       
        /* get value of original series */
        psix = psix1 + dx;
               
        printf("%5.3f %.12f\n", x, psix);
    }
   
    return 0;
}
March 02

Do the Untwist (1006)

Cryptography deals with methods of secret communication that transform a message (the plaintext) into a disguised form (the ciphertext) so that no one seeing the ciphertext will be able to figure out the plaintext except the intended recipient. Transforming the plaintext to the ciphertext is encryption; transforming the ciphertext to the plaintext is decryption. Twisting is a simple encryption method that requires that the sender and recipient both agree on a secret key k, which is a positive integer.

The twisting method uses four arrays: plaintext and ciphertext are arrays of characters, and plaincode and ciphercode are arrays of integers. All arrays are of length n, where n is the length of the message to be encrypted. Arrays are origin zero, so the elements are numbered from 0 to n - 1. For this problem all messages will contain only lowercase letters, the period, and the underscore (representing a space).

The message to be encrypted is stored in plaintext. Given a key k, the encryption method works as follows. First convert the letters in plaintext to integer codes in plaincode according to the following rule: '_' = 0, 'a' = 1, 'b' = 2, ..., 'z' = 26, and '.' = 27. Next, convert each code in plaincode to an encrypted code in ciphercode according to the following formula: for all i from 0 to n - 1,

ciphercode[i] = (plaincode[ki mod n] - i) mod 28.

(Here x mod y is the positive remainder when x is divided by y. For example, 3 mod 7 = 3, 22 mod 8 = 6, and -1 mod 28 = 27. You can use the C '%' operator or Pascal 'mod' operator to compute this as long as you add y if the result is negative.) Finally, convert the codes in ciphercode back to letters in ciphertext according to the rule listed above. The final twisted message is in ciphertext. Twisting the message cat using the key 5 yields the following:

Array 0 1 2
plaintext 'c' 'a' 't'
plaincode 3 1 20
ciphercode 3 19 27
ciphertext 'c' 's' '.'

Your task is to write a program that can untwist messages, i.e., convert the ciphertext back to the original plaintext given the key k. For example, given the key 5 and ciphertext 'cs.', your program must output the plaintext 'cat'.

The input file contains one or more test cases, followed by a line containing only the number 0 that signals the end of the file. Each test case is on a line by itself and consists of the key k, a space, and then a twisted message containing at least one and at most 70 characters. The key k will be a positive integer not greater than 300. For each test case, output the untwisted message on a line by itself.

Note: you can assume that untwisting a message always yields a unique result. (For those of you with some knowledge of basic number theory or abstract algebra, this will be the case provided that the greatest common divisor of the key k and length n is 1, which it will be for all test cases.)

Example input:

5 cs.
101 thqqxw.lui.qswer
3 b_ylxmhzjsys.virpbkr
0

Example output:

cat
this_is_a_secret
beware._dogs_barking
 
Answer
 
#include <stdio.h>
#include <string.h>

int mod(int a, int b)
{
    int r = a % b;
    if (r < 0)
        return r + b;
    return r;
}
 
int main()
{
    int key;
    char plaintext[80];
    char ciphertext[80];
    int ciphercode[80];
    int len;
    int i, j;
 
#ifndef ONLINE_JUDGE
    freopen("1006.in", "r", stdin);
#endif
 
    while (scanf("%d", &key) && key > 0)
    {
        scanf("%s", ciphertext);
        len = strlen(ciphertext);
 
        /* turn ciphertext to ciphercode */
        for (i = 0; i < len; i++)
        {
            if ('_' == ciphertext[i])
                ciphercode[i] = 0;
            else
                if ('.' == ciphertext[i])
                    ciphercode[i] = 27;
                else
                    ciphercode[i] = ciphertext[i] - 'a' + 1;
        }
 
        for (i = 0; i < len; i++)
        {
            /* untwist ciphercode to plaincode */
            j = mod(ciphercode[i] + i, 28);
            /* turn plaincode to plaintext */
            if (0 == j)
                plaintext[mod(key * i, len)] = '_';
            else
                if (27 == j)
                    plaintext[mod(key * i, len)] = '.';
                else
                    plaintext[mod(key * i, len)] = (char)(j - 1 + 'a');
        }
       
        /* output plaintext */
        plaintext[len] = '\0';
        printf("%s\n", plaintext);
    }
 
    return 0;
}
March 01

Love

Love is patient, love is kind. It does not envy, it does not boast, it is not proud. It is not rude, it is not self-seeking, it is not easily angered, it keeps no record of wrongs. Love does not delight in evil but rejoices with the truth. It always protects, always trusts, always hopes, always perseveres.  -- "Corinthians 14-13"
 
爱是恒久忍耐,又有恩慈;爱是不嫉妒,爱是不自夸,不张狂,不作害羞的事,不求自己的益处,不轻易发怒,不计算人的恶,不喜欢不义,只喜欢真理;凡事包容,凡事相信,凡事盼望,凡事忍耐;爱是永不止息。 -- 《新约·哥林多前书》第13章