輝夜 的个人资料Kaguya Houraisan's glori...照片日志列表更多 工具 帮助

日志


2009年11月

USACO 5.2.3 Wisconsin Squares

好长时间没做题了,拿这个来练练手...
 
时限居然有 5s ,看来得做好极限优化的准备。
开始想的是用一个 int [4][4][5] 数组记录每格周围有同种羊的格数,然后 dfs 时每放一格就改变周围 8 格的数字。运行下貌似很慢,在我的机器上都 10s 多。
于是想到以前写过五子棋的位运算优化,这题一共 16 格,每格数字最多不大于 4,因此可用一个 64位 unsigned int 放下,每格数字只用 4 bit 表示即可。位运算的优点是可同时操作 64 位,这样只用一句便可同时改变周围 8 格的数据。
可惜在我的机器上依然跑到了 6s,考虑到还开着耗 CPU 的 APE 在听,正常应该能跑进 5s 吧。结果提交一看在评测机上只用了 2.1s 。。。哦 ... 咱的机器看来该淘汰了 = = 按 6:2 来算不加位运算优化的估计也能进 5s 了...
 
TASK: wissqu
LANG: C++
 
Compiling...
Compile: OK
 
Executing...
   Test 1: TEST OK [2.117 secs, 2940 KB]
 
All tests OK.
 
#include<cstdlib>
#include<fstream>
#include<iterator>
#include<algorithm>
using namespace std;

class farm{
    int cnt, cowat[16], cowleft[5];
    bool nochange[16];
    unsigned long long ajtcnt[5];
    const unsigned long long mask, maskl, maskr;
    pair<int, int> putorder[16];
    int dfscow[16], dfspos[16];

    void addcow(int cowidx, int posidx){
        switch(posidx){
            /*
                0  1  2  3
                4  5  6  7
                8  9 10 11
               12 13 14 15
            */
            case 0:
            case 4:
                ajtcnt[cowidx] += maskl >> (5 - posidx) * 4;
                break;
            case 3:
                ajtcnt[cowidx] += maskr >> (5 - posidx) * 4;
                break;
            case 1:
            case 2:
                ajtcnt[cowidx] += mask >> (5 - posidx) * 4;
                break;
            case 5:
            case 6:
            case 9:
            case 10:
            case 13:
            case 14:
                ajtcnt[cowidx] += mask << (posidx - 5) * 4;
                break;
            case 8:
            case 12:
                ajtcnt[cowidx] += maskl << (posidx - 5) * 4;
                break;
            case 7:
            case 11:
            case 15:
                ajtcnt[cowidx] += maskr << (posidx - 5) * 4;
                break;
        }
        cowat[posidx] = cowidx;
    }
    int replace(int newcow, int posidx){
        int oldcow = cowat[posidx];
        unsigned long long m;
        switch(posidx){
            case 0:
            case 4:
                m = maskl >> (5 - posidx) * 4;
                ajtcnt[newcow] += m;
                ajtcnt[oldcow] -= m;
                break;
            case 3:
                m = maskr >> (5 - posidx) * 4;
                ajtcnt[newcow] += m;
                ajtcnt[oldcow] -= m;
                break;
            case 1:
            case 2:
                m = mask >> (5 - posidx) * 4;
                ajtcnt[newcow] += m;
                ajtcnt[oldcow] -= m;
                break;
            case 5:
            case 6:
            case 9:
            case 10:
            case 13:
            case 14:
                m = mask << (posidx - 5) * 4;
                ajtcnt[newcow] += m;
                ajtcnt[oldcow] -= m;
                break;
            case 8:
            case 12:
                m = maskl << (posidx - 5) * 4;
                ajtcnt[newcow] += m;
                ajtcnt[oldcow] -= m;
                break;
            case 7:
            case 11:
            case 15:
                m = maskr << (posidx - 5) * 4;
                ajtcnt[newcow] += m;
                ajtcnt[oldcow] -= m;
                break;
        }
        cowat[posidx] = newcow;
        return oldcow;
    }
    bool putable(int cowidx, int posidx) const {
        return !((ajtcnt[cowidx] >> posidx*4) & 0xF);
    }
    void dfs(int idx){
        int oldcow;
        if(idx == 16){
            if(!(cnt++)){
                for(int i = 0; i < 16; ++i)
                    putorder[i] = make_pair(dfscow[i], dfspos[i]);
            }
            return;
        }
        int &cow(dfscow[idx]), &pos(dfspos[idx]);
        for(cow = 0; cow < 5; ++cow)if(cowleft[cow]){
            --cowleft[cow];
            for(pos = 0; pos < 16; ++pos)if(nochange[pos] && putable(cow, pos)){
                nochange[pos] = false;
                oldcow = replace(cow, pos);
                dfs(idx + 1);
                replace(oldcow, pos);
                nochange[pos] = true;
            }
            ++cowleft[cow];
        }
    }
public:
    template<class Iter>
    farm(Iter beg, Iter end): cnt(0), mask (0x011101110111ULL),
        maskl(0x011001100110ULL), maskr(0x001100110011ULL){
        fill_n(ajtcnt, 5, 0);
        fill_n(cowleft, 5, 3);
        fill_n(nochange, 16, true);
        for(int i=0; beg != end; ++beg, ++i)
            addcow(*beg - 'A', i);
    }
    void calput(void){
        int oldcow, &pos(dfspos[0]);
        dfscow[0] = 3;
        for(pos = 0; pos < 16; ++pos)if(putable(3, pos)){
            nochange[pos] = false;
            oldcow = replace(3, pos);
            dfs(1);
            replace(oldcow, pos);
            nochange[pos] = true;
        }
    }
    ostream& output(ostream& os){
        for(int i=0; i<16; ++i){
            div_t pos = div(putorder[i].second, 4);
            os << char(putorder[i].first + 'A') << ' '
                << pos.quot + 1 << ' ' << pos.rem + 1 << '\n';
        }
        os << cnt << '\n';
        return os;
    }
};

int main(){
    ifstream fin("wissqu.in");
    farm wissqu((istream_iterator<char>(fin)), istream_iterator<char>());
    fin.close();
    ofstream fout("wissqu.out");
    wissqu.calput();
    wissqu.output(fout);
    fout.close();
}
2009年11月

又一次吃了浮点数的亏 = =

C++:
while(step > 0.02){
    for(;;){
        if(x+step <= 100.0 &&
            (ttlen = ttl(x+step, y)) < minlength){
            minlength = ttlen;
            x += step;
        } else if(x-step >=0.0 &&
            (ttlen = ttl(x-step, y)) < minlength){
            minlength = ttlen;
            x -= step;
        } else if(y+step <= 100.0 &&
            (ttlen = ttl(x, y+step)) < minlength){
            minlength = ttlen;
            y += step;
        } else if(y-step >= 0.0 &&
            (ttlen = ttl(x, y-step)) < minlength){
            minlength = ttlen;
            y -= step;
        } else break;
    }
    step /= 2.0;
}
 
在 x-y 平面上寻找一个对 ttl 函数的极值点。先用 step 为步长寻找,然后步长减半再找,直到满足一定精度为止。
逻辑上过程上都很简单才对,可惜以上代码在 VC2008 运行正确,用 Gcc 却陷入死循环。
 
调试下发现居然出现了这种情况:在 A 点时发现 B 比 A 小,然后在 B点 发现 C 比 B 小,而在 C点 又发现 A 更小……于是就在这几个点上跳不出来了。
其实以前也看过不少强调处理浮点数比较时要特别注意的地方,但每个地方都特别写一下又很麻烦,而且觉得 double 的精度够高应该不用担心,没想到还是出了问题。VC 里大概对浮点数的比较运算做了特殊处理才没出错,感觉在这种很细节的地方 VC 非常强调安全性,但可能有时候也会觉得多此一举或是担心性能上的影响吧。
总之发现问题所在就好解决了,(ttlen = ttl(x, y+step)) < minlength 改为 (ttlen = ttl(x, y+step)) < minlength - 1e-4 即可。
今后再处理浮点数的问题时一定要相当小心咯
2009年10月

File I/O 效率 C vs C++ (一)

继续关注文件读写...这次测试写的效率

其实关于这个问题的讨论似乎总会以类似语言信仰问题而告终,再加 C++ IO 库的复杂性,很多半调子 C++ 程序员总会出现各种误用,反过来却作为攻击 C++ IO 效率低的凭证。
比如经常有人边在输出时大量使用类似
    fout<<...<<endl;
的语句边嚷嚷着写文件速度超慢的,只能说这些人根本不知道自己写的句子都做了什么...
 
所以说在评价任何东西之前先要做到最起码的了解
咱也算是大致研究过 C++ IO stream 各方面的内容,虽然不能说完全掌握仍在学习中,但自认为还是可以写点东西的。
 
总之还是用数据说话。
平台 XPsp3 + VC2008 Express Edition SP1 + STLport / MinGW(GCC4.4.0)
 
实验内容:
共做了三种类型写入的比较,每种类型采用几种不同方法实现:
1. 纯字节流写入
     (1) C fputs()
     (2) C fprintf()
     (3) C++ ofstream<<
     (4) C++ ofstream.rdbuf()->sputn()
2. 宽字符流通过转码写入
     (1) C++ locale + wofstream<<
     (2) C++ codecvt<> facet + ofstream.rdbuf()->sputn()
     (3) WinAPI WideCharToMultiByte() + ofstream<<
3. 格式化写入 (一个 int 一个 double + 一个字符串)
     (1) C fprintf()
     (2) C++ ofstream<<
     (3) C++ ostream.rdbuf()->sputn() sputc() + num_put<> facet
 
前两类比较时写入的内容是完全一致的,也可以横向做下参照。
 
结果:(由于和硬盘寻道时间等也有关系,尽量多次测试取受影响最小的值)
以 C Runtime library 的 fputs 函数所用时间为 1.0 做基准

VC2008EE + VC STL:
text out C fputs()                      1.0
text out C fprintf()                    2.0
text out C++ ofstream <<                1.2
text out C++ ofstream rdbuf()->sputn()  0.8
wText out C++ wofstream deflocale      25.0
wText out C++ locale codecvt            7.5
wText out C++ ofstream + WinAPI         1.5
Format data out C fprintf()             2.0
Format data out C++ ofstream  <<        4.0
Format data out C++ ofs num_put facet   3.0
 
首先用 VC 自带的标准库,可以看到直接用 C++ fstream 的 << 效率与 C 库函数相比还是略低一些,但差距并没有到不可忍受的地步,考虑到 C++ IO stream 的各种特性,这些还是可以接受的。
而直接调用下层 rdbuf()->sputn() 函数却比 fputs() 效率更高,让我对 C++ IO 库的进一步优化仍有希望。
在 Unicode 大行其道的今天,宽字符的读写已成为必需,但使用 locale 配合 wstream 来做的话效率确实无法接受,从上面看居然比调用 WinAPI 的方法慢几十倍。我还是严重怀疑 VC STL 的实现有问题,只是换做直接调用 codecvt<> facet 就能提高好几倍效率。
格式化读写一向是 C++ IO stream 被重点诟病的地方,与 fprintf() 函数整相差一倍,即使直接调用 num_put (去掉了构造 ios_base::sentry 对象产生的流同步、空白跳过等操作),仍然有 50% 的差距。
其实从理论上来说,C++ 的格式化读写应当是比 C 的 fprintf() 函数要快的,因为 fprintf() 总要有一个解析格式字符串的过程,这个只能放在运行时,而 C++ 的格式是通过多个连续函数调用控制的,可以在编译时即进行优化。但实践往往存在各种变数 = =
 
VC2008EE + STLport5.2.1:
text out C fputs()                      1.0
text out C fprintf()                    2.0
text out C++ ofstream <<                0.7
text out C++ ofstream rdbuf()->sputn()  0.6
wText out C++ wofstream deflocale       7.5
wText out C++ locale codecvt            7.0
wText out C++ ofstream + WinAPI         1.0
Format data out C fprintf()             2.0
Format data out C++ ofstream  <<        2.0
Format data out C++ ofs num_put facet   1.7
 
STLport 的 C 库函数完全是照搬 VC 的标准运行库,而只是重新实现了整个 C++ 库,所以 C 函数的效率与上一例是相同的,可直接横向比较。
面对这样的结果,我只能说 STLport 太赞了!!再考虑到其他的种种特性,赶紧扔掉 VC STL 全部换用 STLport 吧
不过转码看来确实是一个平台相关的特性,仍然无法比过 WinAPI 的效率

MinGW GCC4.4.0 + libstdc++:
text out C fputs()                      1.5
text out C fprintf()                    2.7
text out C++ ofstream <<                6.0
text out C++ ofstream rdbuf()->sputn()  2.7
mingw libstdc++ doesnot support other locale...
mingw libstdc++ doesnot support other locale...
wText out C++ ofstream + WinAPI         2.1
Format data out C fprintf()             3.0
Format data out C++ ofstream  <<        6.6
Format data out C++ ofs num_put facet   5.8
 
MinGW 貌似比较慢,是因为 MinGW 默认输出按 UTF-8 编码,对中文来说,字节数是 ANSI 的 1.5 倍。只有调用 API 的那个例子保持了 ANSI 编码。
除以 1.5 的话可以看到 C 库函数的效率与 VC 差不多,但 C++ 的效率比 VC 略低,与 STLport 比就差更远了。毕竟 GCC + libstdc++ 在 Win 平台不是原生支持,只作为跨平台的特性这样也足够了。
没有用 MinGW + STLport 做实验,不知能不能达到 VC 的效率。
 
-------------------------------------------------------------
以前使用 C++ 的 IO stream 做输入输出时总担心效率问题,现在有了 STLport 做支持就可以放心大胆的用了。
但可以看到 C++ 的流式 IO 非常依赖于库实现,在各平台上的表现大概不如 C 库函数来得稳定。
而且使用除 "C" 之外的 locale 时效率确实还是有问题,转码的话还是直接调用平台 API 省时省力又高效。MinGW 考虑不引入 locale 部分也是有道理的啊...
 
这次全是 O,下次再比较 I 的情况...
 
最后按惯例是程序代码:
C++语言:
001 // test the performance of C I/O & C++ streams & C++ codecvt facet
002 #include<cstdio>
003 #include<iostream>
004 #include<fstream>
005 #include<locale>
006 #include<cstdlib>
007 #include<ctime>
008 #include<windows.h>
009 using namespace std;
010
011 const int testsize = 50000;
012
013 int main(){
014     char cstr[] = "这是实验字符串这是实验字符串这是实验字符串这是实验字符串这是实验字符串这是实验字符串这是实验字符串这是实验字符串";
015     wchar_t wstr[] = L"这是实验字符串这是实验字符串这是实验字符串这是实验字符串这是实验字符串这是实验字符串这是实验字符串这是实验字符串";
016     char buffer[200];
017     int cstrlen = strlen(cstr);
018     locale defloc("");
019
020     clock_t start;
021     FILE *cfile;
022     ofstream fout;
023     wofstream wfout;
024
025     // pure text ...........................
026     start = clock();
027     cfile = fopen("text_out_C_fputs.txt", "w");
028     for(int i = 0; i < testsize; ++i){
029         fputs(cstr, cfile);
030         fputc('\n', cfile);
031     }
032     fclose(cfile);
033     printf("Text out C fputs: %d\n", clock()-start);
034
035     start = clock();
036     cfile = fopen("test_out_C_fprintf.txt", "w");
037     for(int i = 0; i < testsize; ++i){
038         fprintf(cfile, "%s\n", cstr);
039     }
040     fclose(cfile);
041     printf("Text out C fprintf: %d\n", clock()-start);
042
043     fout.clear();
044     start = clock();
045     fout.open("text_out_Cpp_ofstream.txt");
046     for(int i = 0; i < testsize; ++i){
047         fout << cstr << '\n';
048     }
049     fout.close();
050     printf("Text out Cpp ofstream: %d\n", clock()-start);
051
052     fout.clear();
053     start = clock();
054     fout.open("text_out_Cpp_rdbuf.txt");
055     for(int i = 0; i < testsize; ++i){
056         fout.rdbuf()->sputn(cstr, cstrlen);
057         fout.rdbuf()->sputc('\n');
058     }
059     fout.close();
060     printf("Text out Cpp rdbuf: %d\n", clock()-start);
061
062     // wchar_t text ...............................
063     wfout.clear();
064     start = clock();
065     wfout.open("wtext_out_Cpp_wofs_defloc.txt");
066     wfout.imbue(defloc);
067     for(int i = 0; i < testsize; ++i){
068         wfout << wstr << L'\n';
069     }
070     wfout.close();
071     printf("wText out Cpp wofs with default locale: %d\n",
072         clock()-start);
073
074     fout.clear();
075     start = clock();
076     char *next;
077     const wchar_t *wnext;
078     mbstate_t st;
079     fout.open("wtext_out_Cpp_codecvt_facet.txt");
080     for(int i = 0; i < testsize; ++i){
081         use_facet<codecvt<wchar_t, char, mbstate_t> >(defloc).out(
082             st, wstr, wstr+sizeof(wstr)/2-1, wnext,
083             buffer, buffer+sizeof(buffer)-1, next);
084         fout.rdbuf()->sputn(buffer, next-buffer);
085         fout.rdbuf()->sputc('\n');
086     }
087     fout.close();
088     printf("wText out Cpp ofs with codecvt facet: %d\n",
089         clock()-start);
090
091     fout.clear();
092     start = clock();
093     fout.open("wtext_out_Cpp_ofs_WinAPI.txt");
094     for(int i = 0; i < testsize; ++i){
095         WideCharToMultiByte(CP_ACP, 0, wstr, -1, buffer, 200,
096             NULL, NULL);
097         fout << buffer << '\n';
098     }
099     fout.close();
100     printf("wText out Cpp ofs with WideCharToMultiByte API: %d\n",
101         clock()-start);
102
103     // Format out ...........................................
104     srand((unsigned)time(NULL));
105
106     char datastr[] = "TestDataString实验格式化字符串";
107     start = clock();
108     cfile = fopen("format_data_out_C_fprintf.txt", "w");
109     for(int i = 0; i < testsize; ++i){
110         fprintf(cfile, "%d %lf %s\n", rand(),
111             double(rand())/RAND_MAX, datastr);
112     }
113     fclose(cfile);
114     printf("Format data out C fprintf: %d\n", clock()-start);
115
116     fout.clear();
117     start = clock();
118     fout.open("format_data_out_Cpp_ofstream.txt");
119     for(int i = 0; i < testsize; ++i){
120         fout << rand() << ' ' << double(rand())/RAND_MAX << ' '
121             << datastr << '\n';
122     }
123     fout.close();
124     printf("Format data out Cpp ofstream: %d\n", clock()-start);
125
126     fout.clear();
127     start = clock();
128     fout.open("format_data_out_Cpp_ofs_facet.txt");
129     for(int i = 0; i < testsize; ++i){
130         use_facet<num_put<char> >(locale::classic()).put(
131             ostreambuf_iterator<char>(fout), fout, ' ', (long)rand());
132         fout.rdbuf()->sputc(' ');
133         use_facet<num_put<char> >(locale::classic()).put(
134             ostreambuf_iterator<char>(fout), fout, ' ',
135             double(rand())/RAND_MAX);
136         fout.rdbuf()->sputc(' ');
137         fout.rdbuf()->sputn(datastr, sizeof(datastr) - 1);
138         fout.rdbuf()->sputc('\n');
139     }
140     fout.close();
141     printf("Format data out Cpp ofs with facet: %d\n", clock()-start);
142
143     system("pause");
144 }
2009年10月

万恶的 xxx

于是连 python 也成了关键词
哪天你把 c, java, php, 或者干脆 linux windows 都封了算了。。。
 
2009年10月

fstream 文件 IO 点滴

http://dantvt.is-programmer.com/posts/11949.html

很多时候较大数据量的文件 IO 总是成为瓶颈,为了提高效率,有时想要先将文件大块大块的读入再行处理。下面分析两种惯常的处理手法。

1. 将文件一次性读入 string 中。

貌似 std::getline 、 istream::getline 或是 operator<< operator>> 等都不提供一次读到文件结尾的机制,只有 istreambuf_iterator 可以做到:

ifstream in("input.txt");
string instr((istreambuf_iterator<char>(in)), istreambuf_iterator<char>());

string 的构造函数前一个参数要多加一层 () 以免编译器误认为是函数声明 = = ...

这样读入 string 会随着内容动态增长,空间不足时会触发额外的 realloc 及 copy 操作,为提高效率有必要预分配足够的空间:

ifstream in("input.txt");
in.seekg(0, ios::end);
streampos len = in.tellg();
in.seekg(0, ios::beg);

string instr;
instr.reserve(len);
instr.assign(istreambuf_iterator<char>(in), istreambuf_iterator<char>());

2. 将文件一次性读入 stringstream 中。

filebuf 和 stringbuf 无法直接通过 rdbuf() 重定向,因此从 filebuf 到 stringbuf 需要一次 copy 操作。最简单的方法是直接复制整个 streambuf :

ifstream in("input.txt");
stringstream ss;
ss<<in.rdbuf();

与 string 的情况相同,这里同样也有一个空间 realloc 及 copy 的问题。但 streambuf 的缓冲区不是那么方便操作的,解决方法是我们给他手动指定一个空间:

ifstream in("input.txt");
in.seekg(0, ios::end);
streampos len = in.tellg();
in.seekg(0, ios::beg);

vector<char> buffer(len);
in.read(&buffer[0], len);

stringstream ss;
ss.rdbuf()->pubsetbuf(&buffer[0], len);

最后再顺便 BS 一下 VC 的 STL = =...

虽然 VC 的编译器效率没的说,但被 STL 拖后腿的话不就白搭了嘛。在文件 IO 方面 (fstream) 比起 MinGW (GCC 4.4.0) 带的要慢好几倍。GCC 的 fstream 格式化读写效率与 C 的比已经不分伯仲,以后应该还会有进一步的提升空间 (编译时格式控制 vs 执行时)

另外上面最后一段程序在 VS2008 (VC9.0) 下应该无法得到预想的结果,跟踪进去看了一下,VC 标准库里的 pubsetbuf 函数体居然是空的!内容如下(中间还有一层函数调用):

virtual _Myt *__CLR_OR_THIS_CALL setbuf(_Elem *, streamsize)
        {       // offer buffer to external agent (do nothing)
        return (this);
        }

看来是等着我们来继承了啊 = = 。而在 MinGW (GCC 4.4.0) 中可以得到预期的结果。

2009年10月

国庆愉快

Oh...换一篇,上一篇太不和谐啦
2009年9月

fileden 貌似挂了?

好像几天没连上
不知是不是被 GFW 了,这一段貌似封的挺紧
过了十一再看看
 
果然是被墙了.>>最近 fg U tor puff 全部失效 ... TMd gcd
2009年7月

用 MSVC 9.0 和 MinGW (GCC 3.4.5) 编译 STLport 5.2.1

目前已发现 VC 9.0 所带 STL 的几处缺陷,为了保证在任何 C++ 编译器中使用 STL 时能够表现一致,决定将标准库都统一为 STLport 。
MinGW 下则是为了使用 wstream 的宽字符流。
大致记录下编译过程,以后也能做个参考。
编译版本 STLport-5.2.1
 
一、VS2008 (MSVC 9.0) 下
    (1) 将压缩包解到某文件夹,如 D:\STLport (路径中无空格)
    (2) 启动 VS2008 的命令行模式。(注意不是 Run... 中的 cmd)
         cd D:\STLport
    (3) configure msvc9 -x --with-static-rtl --use-boost D:\lib\boost_1_36_0
         msvc9 指明编译器
         -x 参数指明是 cross-compile ,在 lib 文件夹下会生成一个 vc9 文件夹。加不加无所谓。
         --with-static-rtl 指明编译静态库,编译器参数选择 /MT 时会使用此库。
                 若是需要动态库则改为 --with-dynamic-rtl
                 经试验好像没法同时编译动态和静态库。一次一种也无所谓。
                 但是选择编译静态库时也会生成动态库的文件,不过那些是不可用的,文件名中也有 x 字母表示无效文件
         --use-boost 指明 boost 库的路径。仅使用一下 boost 的几个头文件,所以无需事先编译 boost。路径中不要有空格,不然很麻烦
    (4) cd build\lib
         切换到 build\lib 子目录
    (5) nmake /fmsvc.mak install
         开始编译。需要待一会儿。完成后会在 STLport\lib 和 STLport\bin (动态库时) 下生成 LIB 和 DLL 文件。
    (6) 启动 VS2008 设定 include 目录。注意把 STLport\stlport 的位置放在 VC 自带的 STL 库的前面。完毕~
 
----------------------------
2009.11.1 补充:
最近根据需要又重新编译了一遍 STLport,发现以前写的这篇文章有几个问题。
先是关于 --use-boost 的选项。STLport 在 5.2.1 版上已经停留好久了,而这期间,boost 的版本从 1.36.0 变动到 1.40.0 ,所以 STLport 中 --use-boost 的选项貌似和新版 boost 已经不能很好的协作了。所以不要再指定这个选项即可,本来这个对 Win32 VC 来说就不是很有必要。
其次关于 --with-static-rtl 和 --with-dynamic-rtl ,以前理解是有误的。
--with-static-rtl 这个指明编译时静态链接到 VC 的 C runtime library (即 libcmt.lib),这样编译出来的库,我们以后在 build 自己的程序时选择 /MT 选项会用到。如果没有其他改动的话,默认也是静态链接到 stlport 库的,如果想要静态链接到 C runtime library 的同时,动态链接到 stlportxx.dll ,那么在自己的项目属性中添加 _STLP_USE_DYNAMIC_LIB 的预定义宏即可。此时会用到带有字母 x 的生成文件(在上面说带 x 的是无效文件,是因为当时不会用,呵呵。)
--with-dynamic-rtl 这个指明编译时动态链接到 VC 的 C runtime library (即 MSVCxxx.lib MSVCP90.dll),这样编译出来的库,我们以后在 build 自己程序时选择 /MD 选项会用到。如果没有其他改动的话,默认也是动态链接到 stlport 库(依赖 stlport.5.2.dll),如果想动态链接到 C runtime library 的同时,静态链接到 stlportxx.lib ,那么在项目属性中添加 _STLP_USE_STATIC_LIB 的预定义宏即可。(此时也会用到带 x 字母的另一些文件。)
 
现在编译的话,直接这么做:
(1) configure msvc9 -x --with-dynamic-rtl
(2) 切换到 build/lib 目录,nmake /fmsvc.mak install clean
(3) configure msvc9 -x --with-static-rtl
(4) 切换到 build/lib 目录,nmake /fmsvc.mak install clean
完毕~ 生成文件在 /lib/vc9 和 /bin/vc9 目录下。
 
STLport 用起来真的很爽,效率比 VC9 自带的 STL 库高不少,特别对于 C++ 的 stream IO 操作优化很好,基本上都可以超过 C 库函数的效率。
----------------------------------------
 
不过在 VC 中使用 STLport 也发现点小问题,主要是和 VC 的调试器配合不好,因为 STLport 中一些标准容器的结构变得比较复杂,好像没法在单步调试时方便的观察数据变化。
 
二、MinGW (GCC 3.4.5) 下
        (1) start MSYS & cd /lib/STLport-5.2.1
        (2) configure --with-boost=/lib/boost_1_36_0
            # 设置 boost 路径时中间不要有空格,不然后面会很难办
        (3) cd build/lib
        (4) mingw32-make -f gcc.mak all
            # 要用 mingw 带的那个 make,不要用 MSYS 的那个
            # 编译好后把 .a 和 .dll 文件分别复制到 stlport/lib 和 stlport/bin
            # all 选项默认 build 所有动态库,要编译静态库须如下步指定:
        (5) mingw32-make -f gcc.mak release-static dbg-static stldbg-static
            # 编译三种静态库。同样把 .a 文件复制到 stlport/lib
        (6) 设置 MinGW 的 include:
            这个貌似只能在编译时指定参数 -I 和 -L
        (7) 使用时的参数
            --动态链接:
                g++ xxx.cpp -mthreads -I /lib/STLport5.2.1/stlport -L /lib/STLport5.2.1/lib/mingw -lstlport.5.2
            --静态链接:
                在源文件开头添加:#define _STLP_USE_STATIC_LIB
                g++ xxx.cpp -mthreads -I /lib/STLport5.2.1/stlport -L /lib/STLport5.2.1/lib/mingw -lstlport
                    # 与动态链接版相比最后的库名不带版本号
                    # 要链接 debug 版只需把最后的库名改为相应名称即可,如 -lstlportg
 
 
本来一个月前就写好的。。。结果拜某节日所赐那几天 Space live 被封。。。然后居然拖了这么久 = = 还是懒啊~
 
然后再发点牢骚。看中文书一般总说里面错误成堆措辞不准确什么的,说计算机书都去看原版质量多高,结果这本按说也是 STL 标准学习手册的《C++ Stardand Library : A Tutorial and Reference》里也是错误频频,什么样的都有。。。函数名写错、算法写错、时间复杂度写错、函数返回值true false 搞反的,还有很多印刷错误就不一一列举了。当然书的内容还是很好的,只是看的时候得小心点。。。
2009年5月

海猫 EP4 汉化补丁发布

庆贺!
可惜日语还是没学好,哎~
2009年5月

Win32 + GCC 的一点尝试

前一段试用了 MinGW 5.1.4 (GCC-3.4.5) 感觉还好,但是有两点不满意:
(1) GCC 自带的 libstdc++-v3 不支持宽字符流 (wcin/wcout/wstream...),似乎是与 Win32 平台的 locale 不兼容所致,但仅用 wstring 的话还是可以的。
(2) GCC 版本有点点久远。毕竟 4.x 版都出来几年了。
(3) MSYS 对中文的支持够呛。这个我也认了,反正只用它来编译,文件名都保持鸟文的就没什么问题。
反正免费平台几乎都这样,那帮老外写的系统总是把国际化支持弄得很差,时不时就得担心来个乱码啥的,毕竟人家也不用管那么多,单就那26个字母的话啥时候也不会出问题。
 
当然也有优点
(1) 全套组件免费。不用总为版权问题心存愧疚
     当然 VC 也有免费 Express 版,但好用的工具 Visual Assist、模拟 Vi 编辑器的 ViEmu 等都不是免费的。
(2) 体积小方便携带。一套 MinGW + MSYS 也就 100MB 出头;VC 随便一整就上 GB...
(3) 运行时库默认链接到 MSVCRT.dll,不像 VC 出个新版就整一套新的 MSVCRxx.dll MSVCRPxx.dll 很乱。
     不过我惊异的是 GCC 动态链接出来的文件体积比 VC 静态链接的还大...写小程序的时候对这个还是有点在意的,毕竟完成同样功能的话一个只要几十KB,另一个要几百KB甚至几MB,差别还是有点大。
 
基于以上考虑,决定对 MinGW 系统尝试做点小升级。
对于宽字符流的支持问题,可以换用其他STL库如 STLport。不过不换而直接使用 API 搞定字符转换也麻烦不到哪去。新的 GCC 可以自己编译着试试。
为了编译 GCC 得先编译安装 GMP 和 MPFR 库,比较简单
cd gmp-x.x.x/
mkdir build & cd build
../configure --prefix=/mingw --disable-shared
make
make check (可选)
make install
编译时间还真长,可以看集动画片了。
然后 MPFR 库类似。make 好后只要通过 check 应该就没什么问题了。
 
麻烦的在编译 GCC ,参数真叫一个多,看半天了 documentation 。这个不敢马虎,毕竟设错的话再来一遍要好久...
GCC 的编译正常来要3遍:第1遍用原GCC编译,第2遍用刚生成的 GCC 自己编译自己,第3遍用第2遍生成的继续编译。如果第2遍和第3遍生成的二进制代码一致则编译成功。
可惜试了三次都没完成。几乎都死在 第2遍编译 上了,似乎环境设置哪里还有问题。然后上 MinGW 官网发现已经有发布预编译过的 4.3.0 版了,晕早发现就不去自己编译了,不然后面还有 libstdc++ ,任务量依然很大。
 
然后咱就偷懒只下了 gcc-part-core-4.3.0-mingw32-bin 和 gcc-part-g++-4.3.0-mingw32-bin 两个包,看说明只用覆盖即可。结果编译一个带 iostream 的程序就出问题,一堆头文件说找不到。不得已继续搜,发现去年都已经有人问了,回答是那两个包里文件选择有bug ,得去下最大的包自己把缺的文件挑出来 = =。我X 这问题都出现快一年了你那俩包也不说修复一下。。。然后又下了最大那个包,把 lib/gcc/ming32/4.3.0/include/c++/ext 这个文件夹覆盖一下问题解决。
 
历尽艰险终于用上 GCC 4.3.0 了。。。先别忙着庆祝,稍微编个小程序,结果发现与 3.4.5 版相比生成的文件体积又大了几乎一倍 = =,执行速度嘛就上次那个计算量很大的 ditch 试了一下,确实快了不少 -O3 参数与 VC 编译的相比差距只在 20% 以内,不过这体积嘛 52KB vs. 9KB ...
又试了下 wcout 似乎可以用,至少编译顺利通过,locale 好像也能使用,只可惜一运行仍然不正常,说程序运行时不正常关闭 - -。看来还得找 STLport ...
总之感觉 MinGW 上 4.3.0 版还是不够成熟,看官方文档似乎打算把 GCC 4.3.1 版作为新的 official release 在半年后发布,希望到那时能解决些问题吧。不过 wchar stream 咱就不奢望了。
 
算了,有空再试  STLport + Boost。以前用 VC 编译过一遍 boost。同时用两套系统还是挺麻烦的,同一套库也得准备两遍 - -
2009年5月

从 std::list 中 size() 的时间复杂度引出的讨论...

http://dantvt.is-programmer.com/posts/8313 两边同步吧...

很奇怪的,或者说是一个不应成为问题的问题...
std::list 的 size() 方法时间复杂度是多少?第一感觉应该是 O(1) 没错吧,多一个变量用于储存链表长度应该是很轻易的事情。于是有了下面这段代码:

#include<iostream>
#include<list>
#include<ctime>
using namespace std;

int main(){
    time_t start, finish;
    int num = 0;
    list<int> coll;

    start = clock();
    for(int i=0;i<10000;++i){
        coll.push_back(i);
        num += coll.size();
    }
    finish = clock();
    cout<<finish - start<<"   num:"<<num<<endl;

    coll.clear();
    start = clock();
    for(int i=0;i<10000;++i){
        coll.push_back(i);
    }
    finish = clock();
    cout<<finish - start<<endl;
    return 0;
}

对两个循环分别计时比较。前一个循环只比后一个多了一句 num += coll.size(); 为了使编译器确实生成 list::size() 的代码。
在 MinGW 5.1.4 中 (GCC 3.4.5) 编译结果运行如下:

450   num:50005000
10

可以看到,前一个循环居然比后一个多花了几乎 45 倍的时间...当我把循环次数从 10000 加到 100000 时程序半天没出结果...

由此有理由猜测 std::list 的 size() 方法难道是 O(N) 的?果然,在头文件中发现了这一段:

size_type
size() const
{ return std::distance(begin(), end()); }

 

直接调用 <algorithm> 算法库函数 distance() 计算元素个数……怪不得这么慢。然后又用 VS2008 (VC9.0)编译,结果如下:

30   num:50005000
60

奇怪的是前一个循环居然比后一个还快...不过至少知道 VS2008 (VC9.0)里的 size() 应该是 O(1) 的。同样查看了一下代码,如下:

size_type size() const
    {   // return length of sequence
    return (_Mysize);
    }

_Mysize 是一个 size_type 类型的变量。疑问解决。不过又有了新问题:

--------------- 咱 -- 是 -- 分 -- 隔 -- 线 ------------------

为什么 GCC 里要把 list::size() 的复杂度搞成 O(N)?

一通搜索后终于看到有这样的讨论:关于 list::splice() 函数。

list 是链表结构,它的优势就在于可以 O(1) 的时间复杂度任意插入删除甚至拼接 list 片段(删除时可能不是,因为要释放内存),list::splice() 是一个很强大的功能,它可在任意位置拼接两个 list,这正是 list 的优势。如果我们在类内部以一个变量储存 list 的长度,那么 splice() 之后新 list 的长度该如何确定?这是一个很严峻的问题,如果要在拼接操作时计算拼接部分的长度,那么将把 O(1) 的时间变成 O(N),这么一来 list 相对 vector 的优势就消失殆尽。

面对这个问题,GCC 和 VC 的 STL 库作者们做了不同的选择。GCC 选择舍弃在 list 内部保存元素数量,而在 size() 时直接从头数到尾,这便出现了开头看到的 O(N) 时间才算出 size();相反,VC 中有了变量 _Mysize ,无论在 insert() erase() splice() 或是 push() pop() 时都需要对其做相应修改。在上面的两个试验中已经看出同样是 10000 个 push_back() 操作,VC 花的时间比较长,不过也仅仅是一个 inc 指令,差别很小就是了。上面几种会改变 list 内容的操作中,大部分对元素数量的影响只是 +1 或 -1,只有 splice() 需要计算拼接部分元素个数,这个差别就大了,咱还是继续用实验证明吧:

#include<iostream>
#include<list>
#include<ctime>
using namespace std;

int main(){
    time_t start,finish;
    list<int> col;
    col.push_back(1);
    col.push_back(10000);

    list<int> col2;
    start = clock();
    for(int i=2;i<10000;++i)
        col2.push_back(i);
    finish = clock();
    cout<<finish - start<<endl;

    int num = 0;
    start = clock();
    for(int i=0;i<10000;++i){
        col.splice(++col.begin(),col2,++col2.begin(),--col2.end());
        num += *(++col.begin());
        col2.splice(++col2.begin(),col,++col.begin(),--col.end());
        num += *(++col2.begin());
    }
    finish = clock();
    cout<<finish - start<<"   num:"<<num<<endl;
    return 0;
}

首先是 MinGW (GCC 3.4.5) 的结果:

10
0   num:60000

可以看到 10000 次 push 是 10,相对的 20000 次 splice() 几乎没花时间 = =

然后是 VS2008 (VC9.0):

20
2714   num:60000

差别非常明显,花了2秒多才完成。当我把循环次数改成 100000 后 GCC 仍是眨眼间的事,VC 却长时间运行无结果……

怎么说呢,GCC 显然是追求效率至上,尽量体现出 list 的优势所在,不过我觉得这么一来倒不如干脆不提供 list 的 size() 方法,有需求的程序员可以自己维护一个变量记录长度,以免误认为 size() 是 O(1) 的而犯下严重错误。相对的 VC 强调功能性和整体效率,可能在实际中需要对链表一段内容做 splice() 操作的机会远远小于求 size() 的操作,所以舍弃前者而保留后者,不过要维护 _Mysize 其他相关函数中也增加了开销。一个见仁见智的问题,我觉得还是 GCC 的选择比较好,list 的优势应该保留,但能在 size() 函数处给个 warning 什么的就好了。

我想还有一个选择是这样:在 list 内部用一个 bool 变量指示当前内部 size 值是有效还是无效。在通常操作时 bool 保持 true,这样在 size() 时直接返回原值即可;在 splice() 后将此 bool 值置为 false 并不计算长度,直到最后又有需要 size() 时发现 bool 是 false 则从头再来一遍 distance() 并再将 bool 置为 true。暂时只想出这么一个算是折中的方法,基本上都能保持两边 O(1) 的效率,但相应其他各关于元素数量的函数内部都要多一个判断当前 size 值是有效还是无效并选择是否改变其值。反正总是不能非常完美

嘛...本来只是发现 size() 的效率问题,没想到却扯出这么一桩事出来...也算长知识了吧

2009年5月

VC 在 Win32 平台还是强啊

小试一下 MinGW,最简单装的 5.1.4 版。看了下附带 GCC 是 3.4.5,只是06年的版本,应该是考虑稳定性的问题。现在 GCC 已经 4.4.0 了吧。
试着编译一个 iostream 输入输出的小程序居然有 400+ KB... strip 后还有 270+...
然后把前面做题目的程序试了下,只用 cstdio 的 fscanf fprintf 倒是体积小了很多只有 20KB+,虽然用了 -O3 但是效率比起 VS2008 编译的还是有挺大差距。拿 VS2008 与前两年的 GCC 比是挺不厚道,不过咱也只是随便试试而已。
果然在 Win 平台下 VC 很无敌...
2009年4月

USACO 4.3.2 The Primes

够BT的一道题,改了几遍才过 - -
一开始没想费太多内存觉得稍微预存几种结果应该就差不多了,结果完全低估了数据规模。
只好把所有可能用到的模式全预算出来再循环。结果弄了快300行ORZ...
还有一次犯傻没排序就跑去提交了
 
TASK: prime3
LANG: C++
 
Compiling...
Compile: OK
 
Executing...
   Test 1: TEST OK [0.065 secs, 2984 KB]
   Test 2: TEST OK [0.054 secs, 2988 KB]
   Test 3: TEST OK [0.065 secs, 2984 KB]
   Test 4: TEST OK [0.065 secs, 2988 KB]
   Test 5: TEST OK [0.076 secs, 2984 KB]
   Test 6: TEST OK [0.140 secs, 2984 KB]
   Test 7: TEST OK [0.119 secs, 2988 KB]
   Test 8: TEST OK [0.130 secs, 2988 KB]
   Test 9: TEST OK [0.205 secs, 2988 KB]
   Test 10: TEST OK [0.259 secs, 2988 KB]
 
All tests OK.
Your program ('prime3') produced all correct answers!  This is your
submission #5 for this problem.  Congratulations!
 
要速度,貌似只能模拟填表的过程,并且每步选限制尽可能多的行或列来填。
第1行和第1列限制最多,开头第1位已定,且各位都不能出现0,先把他们填了。
然后选辅对角线(左下到右上),因为开头结尾两位都已定。
再填主对角线,第1位和第3位已定。
下面是第二行、第二列、第四行、第四列,这四种情况都是 1、2、4 位已定
然后第三行和第三列都只剩下最后一位空缺了。最后检查一下第五行和第五列即可。
 
从上面过程看出要预先计算的是:
(1) 所有5位质数且各位数字和满足条件的数
(2) 第1位已知且满足(1),各位都不为0的组合
(3) 1、5位已知且满足(1)的组合
(4) 1、3位已知且满足(1)的组合
(5) 1、2、3、4位已知且满足(1)的组合
 
其他好像有提出先填第5行和第5列的,因为这两行除了满足质数与数字和外 各位只能是 1、3、7、9 四选一。不过我觉得这没有用,例如只要第一列填了质数,则第5行第一位自然就只会是 1、3、7、9 之一了,先填第5行对第1列来说也并没有其他额外限制作用。但仅是猜测,也没试验过。
 
预先开个100000的数组,第一遍求所有质数(顺便将不是质数的值存为下标方便遍历),然后筛选数字和满足条件的数,同样使下标形成链表方便遍历。同时选出各位数字满足条件的组合预先存起来,剩下只用循环便是。排序嘛,就每位挨个判断了,也没几个数。
2009年4月

VC 中的 STL string 实现

今天小实验一下发现 VC9 的 STL string 实现中没有使用 引用计数和写时拷贝
在 VC6.0 时默认还是有这两个特性的,从 VC7.0 开始貌似就取消了,大概是考虑到线程安全问题
如果我不用多线程,这两个特性应该还是可以一定程度上提高效率的吧
不然在返回一个 string 时是否也要考虑用 auto_ptr 了呢...
看来有必要自己实现一个 string ,或者偶尔考虑用一下其他库的...
 
不过从一致性考虑,感觉还是显式使用智能指针实现以上功能比较好,不然其他各种自定类型是不是也有必要封装一个 share_ptr 类似特性呢。
2009年4月

USACO 4.2.1 Ditch 网络最大流问题算法小结

http://dantvt.is-programmer.com/posts/7974.html
复制算了。MSN 居然能认出全部格式...不错

通过 USACO 4.2.1 Ditch 学习一下最大流算法 。可惜它给的测试数据几乎没有任何杀伤力,后面测试时我们采用 DD_engi 写的程序生成的加强版数据。

总体上来说,最大流算法分为两大类:增广路 (Augmenting Path) 和预流推进重标号 (Push Relabel)。也有算法同时借鉴了两者的长处,如 Improved SAP。本篇主要介绍增广路类算法,思想、复杂度及实际运行效率比较,并试图从中选择一种兼顾代码复杂度和运行效率的较好方案。以下我们将会看到,有时理论分析的时间复杂度并不能很好的反映一种算法的实际效率。

1. Ford - Fulkerson 方法

所有增广路算法的基础都是 Ford - Fulkerson 方法。称之为方法而不是算法是因为 Ford - Fulkerson 只提供了一类思想,在此之上的具体操作可有不同的实现方案。

给定一个有向网络 G(V,E) 以及源点 s 终点 t ,FF 方法描述如下:

Ford-Fulkerson 方法 (G,s,t)
1 将各边上流量 f 初始化为 0
2 while 存在一条增广路径 p
3     do 沿路径 p 增广流量 f
4 return f

假设有向网络 G 中边 (i,j) 的容量为 c(i,j) ,当前流量为 f(i,j) ,则此边的剩余流量即为 r(i,j) = c(i,j) - f(i,j) ,其反向边的剩余流量为 r(j,i) = f(i,j) 。有向网中所有剩余流量 r(i,j) > 0 的边构成残量网络 Gf ,增广路径 p 即是残量网络中从源点 s 到终点 t 的路径。

沿路径 p 增广流量f的操作基本都是相同的,各算法的区别就在于寻找增广路径 p 的方法不同。例如可以寻找从 s 到 t 的最短路径,或者流量最大的路径。

2. Edmonds - Karp 算法

Shortest Augmenting Path (SAP) 是每次寻找最短增广路的一类算法,Edmonds - Karp 算法以及后来著名的 Dinic 算法都属于此。SAP 类算法可统一描述如下:

Shortest Augmenting Path
1 x <-- 0
2 while 在残量网络 Gx 中存在增广路 s ~> t
3     do 找一条最短的增广路径 P
4        delta <-- min{rij:(i,j) 属于 P}
5        沿 P 增广 delta 大小的流量
6        更新残量网络 Gx
7 return x

在无权边的有向图中寻找最短路,最简单的方法就是广度优先搜索 (BFS),E-K 算法就直接来源于此。每次用一遍 BFS 寻找从源点 s 到终点 t 的最短路作为增广路径,然后增广流量 f 并修改残量网络,直到不存在新的增广路径。

E-K 算法的时间复杂度为 O(VE2),由于 BFS 要搜索全部小于最短距离的分支路径之后才能找到终点,因此可以想象频繁的 BFS 效率是比较低的。实践中此算法使用的机会较少。

3. Dinic 算法

BFS 寻找终点太慢,而 DFS 又不能保证找到最短路径。1970年 Dinic 提出一种思想,结合了 BFS 与 DFS 的优势,采用构造分层网络的方法可以较快找到最短增广路,此算法又称为阻塞流算法 (Blocking Flow Algorithm)。

首先定义分层网络 AN(f)。在残量网络中从源点 s 起始进行 BFS,这样每个顶点在 BFS 树中会得到一个距源点 s 的距离 d,如 d(s) = 0,直接从 s 出发可到达的点距离为 1,下一层距离为2 ... 。称所有具有相同距离的顶点位于同一层,在分层网络中,只保留满足条件 d(i) + 1 = d(j) 的边,这样在分层网络中的任意路径就成为到达此顶点的最短路径。

Dinic 算法每次用一遍 BFS 构建分层网络 AN(f),然后在 AN(f) 中一遍 DFS 找到所有到终点 t 的路径增广;之后重新构造 AN(f),若终点 t 不在 AN(f) 中则算法结束。DFS 部分算法可描述如下:

 1 p <-- s
 2 while s 的出度 > 0 do
 3     u <-- p.top
 4     if u != t then
 5         if u 的出度 > 0 then
 6             设 (u,v) 为 AN(f) 中一条边
 7             p <-- p, v
 8         else
 9             从 p 和 AN(f) 中删除点 u 以及和 u 连接的所有边
10     else
11         沿 p 增广
12         令 p.top 为从 s 沿 p 可到达的最后顶点
13 end while

 实际代码中不必真的用一个图来存储分层网络,只需保存每个顶点的距离标号并在 DFS 时判断 dist[i] + 1 = dist[j] 即可。Dinic 的时间复杂度为 O(V2E)。由于较少的代码量和不错的运行效率,Dinic 在实践中比较常用。具体代码可参考 DD_engi 网络流算法评测包中的标程,这几天 dinic 算法的实现一共看过和比较过将近 10 个版本了,DD 写的那个在效率上是数一数二的,逻辑上也比较清晰。

 4. Improved SAP 算法

 本次介绍的重头戏。通常的 SAP 类算法在寻找增广路时总要先进行 BFS,BFS 的最坏情况下复杂度为 O(E),这样使得普通 SAP 类算法最坏情况下时间复杂度达到了 O(VE2)。为了避免这种情况,Ahuja 和 Orlin 在1987年提出了Improved SAP 算法,它充分利用了距离标号的作用,每次发现顶点无出弧时不是像 Dinic 算法那样到最后进行 BFS,而是就地对顶点距离重标号,这样相当于在遍历的同时顺便构建了新的分层网络,每轮寻找之间不必再插入全图的 BFS 操作,极大提高了运行效率。国内一般把这个算法称为 SAP...显然这是不准确的,毕竟从字面意思上来看 E-K 和 Dinic 都属于 SAP,我还是习惯称为 ISAP 或改进的 SAP 算法。

 与 Dinic 算法不同,ISAP 中的距离标号是每个顶点到达终点 t 的距离。同样也不需显式构造分层网络,只要保存每个顶点的距离标号即可。程序开始时用一个反向 BFS 初始化所有顶点的距离标号,之后从源点开始,进行如下三种操作:(1)当前顶点 i 为终点时增广 (2) 当前顶点有满足 dist[i] = dist[j] + 1 的出弧时前进 (3) 当前顶点无满足条件的出弧时重标号并回退一步。整个循环当源点 s 的距离标号 dist[s] >= n 时结束。对 i 点的重标号操作可概括为 dist[i] = 1 + min{dist[j] : (i,j)属于残量网络Gf}。具体算法描述如下:

algorithm Improved-Shortest-Augmenting-Path
 1 f <-- 0
 2 从终点 t 开始进行一遍反向 BFS 求得所有顶点的起始距离标号 d(i)
 3 i <-- s
 4 while d(s) < n do
 5     if i = t then    // 找到增广路
 6         Augment
 7         i <-- s      // 从源点 s 开始下次寻找
 8     if Gf 包含从 i 出发的一条允许弧 (i,j)
 9         then Advance(i)
10         else Retreat(i)    // 没有从 i 出发的允许弧则回退
11 return f

procedure Advance(i)
1(i,j) 为从 i 出发的一条允许弧
2 pi(j) <-- i    // 保存一条反向路径,为回退时准备
3 i <-- j        // 前进一步,使 j 成为当前结点

procedure Retreat(i)
1 d(i) <-- 1 + min{d(j):(i,j)属于残量网络Gf}    // 称为重标号的操作
2 if i != s
3     then i <-- pi(i)    // 回退一步

procedure Augment
1 pi 中记录为当前找到的增广路 P
2 delta <-- min{rij:(i,j)属于P}
3 沿路径 P 增广 delta 的流量
4 更新残量网络 Gf

 算法中的允许弧是指在残量网络中满足 dist[i] = dist[j] + 1 的弧。Retreat 过程中若从 i 出发没有弧属于残量网络 Gf 则把顶点距离重标号为 n 。

 虽然 ISAP 算法时间复杂度与 Dinic 相同都是 O(V2E),但在实际表现中要好得多。要提的一点是关于 ISAP 的一个所谓 GAP 优化。由于从 s 到 t 的一条最短路径的顶点距离标号单调递减,且相邻顶点标号差严格等于1,因此可以预见如果在当前网络中距离标号为 k (0 <= k < n) 的顶点数为 0,那么可以知道一定不存在一条从 s 到 t 的增广路径,此时可直接跳出主循环。在我的实测中,这个优化是绝对不能少的,一方面可以提高速度,另外可增强 ISAP 算法时间上的稳定性,不然某些情况下 ISAP 会出奇的费时,而且大大慢于 Dinic 算法。相对的,初始的一遍 BFS 却是可有可无,因为 ISAP 可在循环中自动建立起分层网络。实测加不加 BFS 运行时间差只有 5% 左右,代码量可节省 15~20 行。

 5. 最大容量路径算法 (Maximum Capacity Path Algorithm)

 1972年还是那个 E-K 组合提出的另一种最大流算法。每次寻找增广路径时不找最短路径,而找容量最大的。可以预见,此方法与 SAP 类算法相比可更快逼近最大流,从而降低增广操作的次数。实际算法也很简单,只用把前面 E-K 算法的 BFS 部分替换为一个类 Dijkstra 算法即可。USACO 4.2 节的说明详细介绍了此算法,这里就不详述了。

 时间复杂度方面。BFS 是 O(E),简单 Dijkstra 是 O(V2),因此效果可想而知。但提到 Dijkstra 就不能不提那个 Heap 优化,虽然 USACO 的算法例子中没有用 Heap ,我自己还是实现了一个加 Heap 的版本,毕竟 STL 的优先队列太好用了不加白不加啊。效果也是非常明显的,但比起 Dinic 或 ISAP 仍然存在海量差距,这里就不再详细介绍了。

 6. Capacity Scaling Algorithm

 不知道怎么翻比较好,索性就这么放着吧。叫什么的都有,容量缩放算法、容量变尺度算法等,反正就那个意思。类似于二分查找的思想,寻找增广路时不必非要局限于寻找最大容量,而是找到一个可接受的较大值即可,一方面有效降低寻找增广路时的复杂度,另一方面增广操作次数也不会增加太多。时间复杂度 O(E2logU) 实际效率嘛大约稍好于最前面 BFS 的 E-K 算法,稀疏图时表现较优,但仍然不敌 Dinic 与 ISAP。

 7. 算法效率实测!

 重头戏之二,虽然引用比较多,哎~

 首先引用此篇强文 《Maximum Flow: Augmenting Path Algorithms Comparison》

 对以上算法在稀疏图、中等稠密图及稠密图上分别进行了对比测试。直接看结果吧:

稀疏图:

ISAP 轻松拿下第一的位置,图中最左边的 SAP 应该指的是 E-K 算法,这里没有比较 Dinic 算法是个小遗憾吧,他把 Dinic 与 SAP 归为一类了。最大流量路径的简单 Dijkstra 实现实在是太失败了 - -,好在 Heap 优化后还比较能接受……可以看到 Scaling 算法也有不错的表现。

 中等稠密图:

 

 ISAP 依然领先。最大流量算法依然不太好过……几个 Scaling 类算法仍然可接受。

 稠密图:

 

 ISAP……你无敌了!这次可以看出 BFS 的 Scaling 比 DFS 实现好得多,而且几乎与 Improved Scaling 不相上下,代码量却差不少。看来除 ISAP 外 BFS Scaling 也是个不错的选择。

 最后补个我自己实测的图,比较算法有很多是 DD 网络流算法评测包里的标程,评测系统用的 Cena,评测数据为 DD ditch 数据生成程序生成的加强版数据:

 

 我一共写了 7 个版本的 ISAP ,Dinic 算法也写了几个递归版的但效率都不高,只放上来一个算了。从上图来看似乎 ISAP 全面超越了号称最大流最快速算法的 HLPP,但在另外一台机器上测试结果与此却不大相同,有时 ISAP 与 HLPP 基本持平,有时又稍稍慢一些。在这种差距非常小的情况下似乎编译器的效果也比较明显。那个 HLPP 是用 PASCAL 写的,我不知在 Win32 平台下编译代码效率如何,至少我的几个 ISAP 用 VC2008 + SP1 编译比用 g++ 要强不少,也可能是参数设置的问题。

不过这些都是小事,关键是见证了 ISAP 的实际效率。从上面可以看出不加 GAP 优化的 ISAP 有几个测试点干脆无法通过,而不加 BFS 却无甚大影响,递归与非递归相差在 7% 左右的样子。综合以上表现,推荐采用 ISAP 不加 BFS,非递归 + GAP 优化的写法,Ditch 这道题一共也才 80 行左右代码。要提的一点是 GAP 优化用递归来表现的话不如 while 循环来得直接。另外,看起来 HLPP 表现确实很优秀,有机会也好好研究一下吧,预流推进重标号算法也是一大类呢……

最后附上一个 ISAP + GAP + BFS 的非递归版本代码,网络采用邻接表 + 反向弧指针:

  1. #include<cstdio>
  2. #include<memory>
  3. using namespace std;
  4.  
  5. const int maxnode = 1024;
  6. const int infinity = 2100000000;
  7.  
  8. struct edge{
  9.     int ver;    // vertex
  10.     int cap;    // capacity
  11.     int flow;   // current flow in this arc
  12.     edge *next; // next arc
  13.     edge *rev;  // reverse arc
  14.     edge(){}
  15.     edge(int Vertex, int Capacity, edge *Next)
  16.         :ver(Vertex), cap(Capacity), flow(0), next(Next), rev((edge*)NULL){}
  17.     void* operator new(size_t, void *p){
  18.         return p;
  19.     }
  20. }*Net[maxnode];
  21. int dist[maxnode]= {0}, numbs[maxnode] = {0}, src, des, n;
  22.  
  23. void rev_BFS(){
  24.     int Q[maxnode], head = 0, tail = 0;
  25.     for(int i=1; i<=n; ++i){
  26.         dist[i] = maxnode;
  27.         numbs[i] = 0;
  28.     }
  29.  
  30.     Q[tail++] = des;
  31.     dist[des] = 0;
  32.     numbs[0] = 1;
  33.     while(head != tail){
  34.         int v = Q[head++];
  35.         for(edge *e = Net[v]; e; e = e->next){
  36.             if(e->rev->cap == 0 || dist[e->ver] < maxnode)continue;
  37.             dist[e->ver] = dist[v] + 1;
  38.             ++numbs[dist[e->ver]];
  39.             Q[tail++] = e->ver;
  40.         }
  41.     }
  42. }
  43.  
  44. int maxflow(){
  45.     int u, totalflow = 0;
  46.     edge *CurEdge[maxnode], *revpath[maxnode];
  47.     for(int i=1; i<=n; ++i)CurEdge[i] = Net[i];
  48.     u = src;
  49.     while(dist[src] < n){
  50.         if(u == des){    // find an augmenting path
  51.             int augflow = infinity;
  52.             for(int i = src; i != des; i = CurEdge[i]->ver)
  53.                 augflow = min(augflow, CurEdge[i]->cap);
  54.             for(int i = src; i != des; i = CurEdge[i]->ver){
  55.                 CurEdge[i]->cap -= augflow;
  56.                 CurEdge[i]->rev->cap += augflow;
  57.                 CurEdge[i]->flow += augflow;
  58.                 CurEdge[i]->rev->flow -= augflow;
  59.             }
  60.             totalflow += augflow;
  61.             u = src;
  62.         }
  63.  
  64.         edge *e;
  65.         for(e = CurEdge[u]; e; e = e->next)
  66.             if(e->cap > 0 && dist[u] == dist[e->ver] + 1)break;
  67.         if(e){    // find an admissible arc, then Advance
  68.             CurEdge[u] = e;
  69.             revpath[e->ver] = e->rev;
  70.             u = e->ver;
  71.         } else {    // no admissible arc, then relabel this vertex
  72.             if(0 == (--numbs[dist[u]]))break;    // GAP cut, Important!
  73.             CurEdge[u] = Net[u];
  74.             int mindist = n;
  75.             for(edge *te = Net[u]; te; te = te->next)
  76.                 if(te->cap > 0)mindist = min(mindist, dist[te->ver]);
  77.             dist[u] = mindist + 1;
  78.             ++numbs[dist[u]];
  79.             if(u != src)
  80.                 u = revpath[u]->ver;    // Backtrack
  81.         }
  82.     }
  83.     return totalflow;
  84. }
  85.  
  86. int main(){
  87.     int m, u, v, w;
  88.     freopen("ditch.in", "r", stdin);
  89.     freopen("ditch.out", "w", stdout);
  90.     while(scanf("%d%d", &m, &n) != EOF){    // POJ 1273 need this while loop
  91.         edge *buffer = new edge[2*m];
  92.         edge *data = buffer;
  93.         src = 1; des = n;
  94.         while(m--){
  95.             scanf("%d%d%d", &u, &v, &w);
  96.             Net[u] = new((void*) data++) edge(v, w, Net[u]);
  97.             Net[v] = new((void*) data++) edge(u, 0, Net[v]);
  98.             Net[u]->rev = Net[v];
  99.             Net[v]->rev = Net[u];
  100.         }
  101.         rev_BFS();
  102.         printf("%d\n", maxflow());
  103.         delete [] buffer;
  104.     }
  105.     return 0;
  106. }

2009年3月

Sound Horizon - Elysion ~楽園幻想物語組曲~

译文转自Popgo
http://popgo.net/bbs/showthread.php?s=&threadid=413602

对歌词的说明:

1.带下划线的词,实际须念成下划线后( )中的词。如:乐园(Elysion),念作Elysion。

2.歌中的念白,用白字表示。
  歌中的唱词,用蓝色表示。
  歌中作为背景声的唱词或念白,用粉色表示。
  极少数有二人对唱的部分,用其它两种不同彩色表示。

欢迎转载,但转载须保留本段
玖羽 2006.8.22

演出:

作词·作曲·编曲:
    ——Revo
主唱·女性念白:     ——Aramary
假面男人ABYSS:   ——Jimang (#1,3,4,7,9,11)
女高音和声:          ——Miyoshi Harito (#3,4,8,11)

=========================================
=========================================
01 - エルの楽園 [→ Side:E →]
   

01. EL的乐园 [→ Side:E →]

私は…生涯彼女を愛することはないだろう…
我…大概这一辈子都不会爱她…
しかし…彼女という存在は…私にとって特別な意味を孕むだろう…
但…那个被称为“她”的人…对我来说,也许孕育着极特别的意义…
何故なら…生まれてくる娘の名は…遠い昔にもう決めてあるのだから…
为什么呢…因为那生下来的女儿的名字…在很久以前就已经定好了…

──そして…幾度目かの楽園の扉が開かれる……
——于是…乐园的门扉不知第几次被打开……

(Elysion, who ah... Elysion, who ah...)

白い大地に 緋い雫で 描かれた軌跡 罪の道標
白色的大地 绯红的血滴 绘出的轨迹 是罪孽的路标
古びた金貨(コイン) 握りしめたまま 這い擦りながらも 男は笑った
古旧的金币(Coin) 在手中紧握 艰难地爬着 那男人笑了

廻るように 浮かんでくる 愛しい笑顔 すぐ其処に
仿佛盘旋着 浮现在眼前 可爱的笑脸 就快要看见
無限の果てに 手を伸ばす様に 扉に手を掛けた
如同向梦幻的尽头 伸出手一般 那手扶上门扉


──そして…彼の現実は朽ち果てる……
——于是…他的现实朽腐了……
(Come Down to the Elysion)

少女が小さく 咳をする度 胸の痛みが 春を遠ざける
少女每一次 轻轻地咳嗽 胸口的疼痛 都使春天远离
襤褸い毛布でも 夢は見られる 愛を知った日の 温もり忘れない
裹在破烂(诅咒)的毛毯中 依然做着梦 知晓爱的日子 温暖难忘

眠るように 沈んでゆく 愛しい世界 水底に
仿佛睡觉一样 缓缓沉入 可爱的世界 的水底
夢幻の果てが 手を招く様に 扉は開かれた
如同在梦幻的尽头 招着手一般 那门扉打开了

──そして…彼女の現実は砕け散る……
——于是…她的现实碎散了……
(Come Down to the Elysion)

ねぇ…お父様(パパ) その楽園ではどんな花が咲くの?
喂……爸爸 要是在乐园里的话,会有怎样的花在开着哪?
ねぇ…お父様(パパ) その楽園ではどんな鳥が歌うの?
喂……爸爸 要是在乐园里的话,会有怎样的鸟在唱着哪?
ねぇ…お父様(パパ) その楽園では体はもう痛くないの?
喂……爸爸 要是在乐园里的话,这身体就不会再痛了吧?
ねぇ…お父様(パパ) その楽園ではずっと一緒にいられるの?
喂……爸爸 要是在乐园里的话,我们就永远在一起了吧?
ねぇ…お父様(パパ)…
喂……爸爸…


窓を叩く夜風 弾む吐息 薄暗い部屋 楽しそうな談笑
窗外的夜风 急促的吐息 微暗的房间 似乎很快乐的谈笑
虚ろな月明かり 白い吐息 薄汚い部屋 痩せた膝の少女
空虚的月光 白色的吐息 微脏的房间 那双膝瘦弱的少女

幾度となく繰り返される問い掛け 尽きることのない『楽園』への興味
多少次反反复复地问着 对“乐园”无尽的兴趣
嗚呼…少女にはもう見えていないのだ 傍らに横たわるその屍体が…
啊…少女已经看不到了 那横倒在身旁的尸体…

「ねえ、お父様(パパ)」
“喂,爸爸”
「なんだい、エル?」
“怎么了,EL?”
「明日はなんの日か知ってる?」
“你知道明天是什么日子吗?”
「世界で一番可愛い女の子の誕生日」
“是世界上最可爱的女孩子的生日啦”

“…我想啊,在生日的时候有画册当礼物就好了…”

(Cross Talk)…男の夢想は残酷な現実となり
(Cross Talk)…男人的梦想变成残酷的现实
(Cross Talk)…少女の現実は幽幻な夢想となる
(Cross Talk)…少女的现实变成幽幻的梦想
(Cross Talk)…男の楽園は永遠の奈落となり
(Cross Talk)…男人的乐园变成永恒的深渊
(Cross Talk)…少女の奈落は束の間の楽園となる
(Cross Talk)…少女的深渊变成刹那的乐园


…お父様(パパ)── その楽園ではどんな恋が咲くの?
…爸爸—— 要是在乐园里的话,会有怎样的恋情开着哪?
ねぇ…お父様(パパ) その楽園ではどんな愛を歌うの?
喂……爸爸 要是在乐园里的话,会有怎样的爱意唱着哪?
…お父様(パパ)── その楽園では心はもう痛くないの?
…爸爸—— 要是在乐园里的话,这心中就不会再痛了吧?
ねぇ…お父様(パパ) その楽園ではずっと一緒にいられるの?
喂……爸爸 要是在乐园里的话,我们就永远在一起了吧?

ねぇ…お父様(パパ) その楽園ではどんな花が咲くの?
喂……爸爸 要是在乐园里的话,会有怎样的花在开着哪?
ねぇ…お父様(パパ) その楽園ではどんな鳥が歌うの?
喂……爸爸 要是在乐园里的话,会有怎样的鸟在唱着哪?
ねぇ…お父様(パパ) その楽園では体はもう痛くないの?
喂……爸爸 要是在乐园里的话,这身体就不会再痛了吧?
ねぇ…お父様(パパ) その楽園ではずっと一緒にいられるの?
喂……爸爸 要是在乐园里的话,我们就永远在一起了吧?

ねぇ…お父様(パパ)…
喂……爸爸…


(Elysion, who ah...Elysion, who ah...)
(Elysion, who ah...Elysion, who ah...)

=========================================
=========================================
02 - Ark
   

02. Ark

「彼女こそ…私のエリスなのだろうか…」
“只有她…才是我的Alice吧…”

「──箱庭を騙る檻の中で 禁断の海馬(器官)に手を加えて
“——在伪装成盆景的牢笼之中 将禁忌的海马体(器官)加工
驕れる無能な創造神(かみ)にでも成った心算なの…」
要成为自大而又无能的造物主(神) 只要这样就可以了……”

…Love wishing to the "Ark"


(崩壊 其れは孕み続けた季節 二月の雪の日 『妹』(Soror)の記憶(ゆめ))
崩溃 那是一直在孕育的季节 二月里下雪的日子 『妹』(Soror)的记忆(梦)

「我々を楽園へ導ける箱舟は 哀れなる魂を大地から解き放つ
“引导我们向乐园而去的方舟 将悲哀的灵魂从大地上解放
救いを求める貴女にアークを与えよう」
现在就赐给祈求救赎的你以Ark”
『アークと呼ばれた物』(それ)は月光を受けて銀色に煌めいた…
「被称为Ark的东西」(它)在月光下闪耀着银色的光芒…

想い出まで裏切った 冷たい言葉の雨
那冰冷的言辞之雨 将回忆背叛
幸せだった二人 永遠に届かなくなる前に…
幸福的两人 在无法获得永恒之前…

「ねぇ…何故変わってしまったの? あんなにも愛し合っていたのに」
“喂…为什么变了啊? 明明是那么地相爱”
涙を微笑みに換え詰め寄る 『アークと呼ばれた物』(Knife)を握って…
把眼泪换成微笑缓缓靠近 手里握住「被称为Ark的东西」(Knife)

──愛憎の『箱舟』(Ark)
——爱憎之方舟(Ark)


「さぁ…楽園へ帰りましょう、お兄様…」
“来…回到乐园去吧,哥哥…”

(因果 其れは手繰り寄せた糸 六月の雨の日 『兄』(Frater)の記憶(ゆめ))
因果 那是操纵在手中的丝线 六月里下雨的日子 『兄』(Frater)的记忆(梦)

信じてたその人に裏切られた少女
被自己相信的那个人背叛的少女
逃げ込んだ楽園は信仰という狂気
逃入的乐园是被称为信仰的疯狂
新しい世界へと羽ばたける自己暗示
向新世界中展翼飞翔的自我暗示
澄み渡る覚醒は『進行』という凶器
明白的觉醒是被称为恶化的凶器

最期の瞬間に廻った 歪な愛の記憶
最后的瞬间(时刻)中重复的 是扭曲的爱之记忆
脆弱な精神(ココロ)が耐えきれず あの日嘘を吐いた
脆弱的精神(心灵)不堪重负 那一天欺骗的话语…

律すれば律する程堕ちる 赦されぬ想いに灼かれながら
刻骨蚀心的思念焚烧其身 被违心的拒绝痛苦折磨
まぐわう傷は深く甘く 破滅へ誘う…
交欢的伤口深刻而又甜蜜 诱向毁灭之中…

──背徳の『箱舟』(Ark)
——悖德之方舟(Ark)


「さぁ…楽園へ帰りましょう、お兄様…」
“来…回到乐园去吧,哥哥…”

被験体1096 通称『妹』(Soror)同じく
试验体#1096 通称『妹』(Soror)将同为
(Soror with the "Ark", Frater in the Dark)
被験体1076 通称『兄』(Frater)を殺害
试验体#1076 通称『兄』(Frater)者杀害
(Soror with the "Ark", Frater it's Dead)

<症例番号(Case Number)12>
病例编号(Case Number)12>
過剰投影型依存における袋小路の模型(モデル)
过度投射型依赖导致走入死胡同的模型(Model)
即ち『虚妄型箱舟依存症候群』(Ark)
亦即“妄想型方舟依赖症候群”(Ark)

限りなく同一に近づける 追憶は狂気にも似た幻想
无限地追求着合二为一 回忆宛如那疯狂的幻想
求める儘に唇を奪い合い 少しずつ楽園を追われてゆく
竭尽所能地亲吻着嘴唇 一点点从乐园远离流放
同じ心的外傷(トラウマ)重ねれば響き会う けれどそれ以上には…
即使同样的心灵创伤(Trauma)互相重叠回响 但却无法再…

「──箱庭を騙る檻の中で 禁断の海馬(器官)に手を加えて
“——在伪装成盆景的牢笼之中 将禁忌的海马体(器官)加工
驕れる無能な創造神(かみ)にでも成った心算なの…」か…
要成为自大而又无能的造物主(神) 只要这样就可以了”吗…

在りし日に咲かせた花弁は 暗闇に散り逝くように凛と
那往昔悉心浇灌的花瓣 是宛如在黑暗中凋散般地凄凉
少女の声音で響(ささや)く 「楽園へ帰りましょう」…
少女的声音低吟着“回到乐园去吧”…

…Love wishing to the "Ark"


──監視卿(Watcher)は天を仰ぎ深い溜息を吐く
监视官(Watcher)仰天长叹
失った筈の『左手の薬指』(場所)が虚しく疼いた
理应失去的“左手无名指”(地方)空虚地痛着

ふと彼が監視鏡(Monitor)の向こうへ視線を戻すと
——旋即把视线投回到监视镜(Monitor)的方向
嗚呼…いつの間にか少女の背後には仮面の男が立っていた──
啊…不知何时“假面的男人”竟站在少女身后——

=========================================
=========================================
03 - エルの絵本【魔女とラフレンツェ】
   

03. EL的画册 【魔女与Lafrenze】

いかにして 楽園の扉は開かれたのか…
乐园的门扉是怎样打开的呢……

鬱蒼と茂る暗緑の樹々 不気味な鳥の鳴き声
茂密繁盛的暗绿树林 令人不快的鸟的鸣叫
ある人里離れた森に その赤ん坊は捨てられていた
一片远离人烟的森林里 那个孩子被遗弃了

幸か…不幸か…人目を憚(はば)かるように捨てられていたその子を拾ったのは
幸运吗…不幸吗…仿佛是怕别人看见,这个孩子才被遗弃;而把她捡去的是
王国を追われた隻眼の魔女 《深紅の魔女と謳われた》(クリムゾンの)オルドローズ
从王国中放逐的独眼魔女 “被称为深红之魔女的”(Crimson之) Old Rose

銀色の髪に 緋色の瞳 雪のように白い肌
银色的秀发 绯红的眼眸 雪一般洁白的肌肤
拾われた赤ん坊は いつしか背筋が凍る程美しい娘へと育った…
那个被捡回去的孩子 不知不觉地已经长成美得令人背脊发冷的少女了…

流転こそ万物の基本 流れる以上時もまた然り
流转乃是万物的基础 同样在流动的时间亦然
二つの楽園を巡る物語は 人知れず幕を開ける…
在两个乐园之间循环的故事 无人知晓的幕布被拉开…

(可恨啊…让我出去…救救我吧…)

「ラフレンツェや、忘れてはいけないよ。」
“Lafrenze呀…千万不要忘记……”

銀色の髪を風になびかせて 祈るラフレンツェ 死者の為に…
微风吹拂银色的秀发 祈祷吧Lafrenze 为了死者…
小さな唇が奏でる鎮魂歌(レクイエム)  歌えラフレンツェ 永遠に響け…
薄薄双唇编织出的安魂曲(Requiem) 歌唱吧Lafrenze 在永远(永恒)中回荡…


時を食らう大蛇(セルペンス) 灼けた鎖の追走曲(カノン)
吞噬时间的大蛇(Serpens) 灼热锁缚的轮唱曲(Canon)
狂い咲いた曼珠沙華(リコリス) 還れない楽園(エリュシオン)
疯狂盛开的曼珠沙华(Lycoris) 无法归去的乐园(Elysion)
蝋燭が消えれば 渡れない川がある
烛火一旦熄灭 就见到无可渡过的长河
始まりも忘れて 終わらない虚空(そら)を抱く…
将起源遗忘 拥抱着无尽无终的虚空(穹苍)……


――Creature's voice
亡者们的声音
「―おのれラフレンツェ」…
悲痛な叫びの不響和音(ハーモニー)
“——可恶啊Lafrenze”…悲痛地呼喊着的不协和音(Harmony)

――Un-satisfied
无止境的渴望
「――憎きラフレンツェ」…
呪怨の焔は燃ゆる
“——可恨啊Lafrenze”…诅咒怨恨的火焰熊熊燃烧

儚い幻想と知りながら 生者は彼岸に楽園を求め
抱着虚幻的梦想 生者将彼岸的乐园寻求
死者もまた 還れざる彼岸に楽園を求める
而死者也 竭力想回到对岸的乐园
彼らを別つ流れ 深く冷たい冥府の川
将他们分隔的流水 那深冷的冥府之河
乙女の流す涙は 永遠に尽きることなく
女郎流下的眼泪 永远不会停歇
唯…嘆きの川の水嵩を増すばかり…
只是…使叹息之河的水位加增而已…

――少女を悪夢から呼び醒ます 美しき竪琴の調べ
——把少女从恶梦中唤醒的 竖琴美妙的声调
哀しい瞳(め)をした弾き手 麗しきその青年の名は――
眼神哀伤的竖琴手 那英俊的青年名叫……

「ラフレンツェや、忘れてはいけないよ。
“Lafrenze呀…千万不要忘记……
お前は冥府に巣くう亡者共の手からこの世界を護る為の最後の門の番人。
 你是从那盘踞在冥府的亡者们手中守护这个世界的,最后的黄泉守护者。
純潔の結界を破らせてはいけないよ。」
 纯洁的结界,千万不能让它破坏啊……”

祖母が居なくなって 唇を閉ざした
祖母已经不在了 将双唇紧闭
吹き抜ける風 寂しさ孤独と知った
吹过的风 诉说着寂寞的凄凉
彼が訪れて 唇を開いた
当他到来的时候 将双唇轻启
嬉しくなって 誓いも忘れていった…
是那样快乐 连誓言也被遗忘…

――それは手と手が触れあった 瞬間の魔法
——那是 手与手互相碰触 瞬间的魔法
高鳴る鼓動 小さな銀鈴(ベル)を鳴らす
 强烈的心跳 小小的银铃(Bell)轻响
瞳(め)と瞳(め)見つめ合った 瞬間の魔法
 眼与眼互相对视 瞬间的魔法
禁断の焔 少女は恋を知った――
 禁忌的火焰 少女将恋爱知晓…


一つ奪えば十が欲しくなり 十を奪えば百が欲しくなる
夺得了一就想要十 夺得了十更想要百
その焔は彼の全てを 灼き尽くすまで消えはしない…
那火焰即使把他全身 烧尽也不会平息…


「ラフレンツェや 忘れてはいけないよ。」
“Lafrenze呀…千万不要忘记……”

愛欲に咽ぶラフレンツェ 純潔の花を散らして
接受爱欲的Lafrenze 纯洁的花朵被摘下
愛憎も知らぬラフレンツェ 漆黒の焔を抱いて
不知爱憎的Lafrenze 将漆黑的火焰拥抱
彼は手探りで闇に繋がれた 獣の檻を外して
他的手摸索着打开了 黑暗中野兽的牢笼
少女の胎内(なか)に繋がれた 冥府の底へ堕りてゆく…
少女却因腹中之物 堕入冥府的奥深……


――近づいてくる足音
——逐渐接近的脚步声
やがて彼(オルフェウス)が乙女(エウリディケ)の手を引いて 暗闇の階段を駈け上がって来る
终于(Orpheus)牵着女郎(Eurydice)的手 向黑暗的楼梯上跑去
けれど少女は裏切りの代償として 残酷な呪いを歌った
然而背叛了少女的代价 是残酷的诅咒之歌
嗚呼…もう直ぐ彼は…彼は振り返ってしまうだろう
啊…很快他就…他就要回过头来了——

――魔女がラフレンツェを生んだのか ラフレンツェが魔女を生んだのか――
——是魔女变成了Lafrenze呢…还是Lafrenze变成了魔女…
――物語は本(ページ)の外側に――
——故事走到了书页之外…

就这样…乐园的门扉打开了……

=========================================
=========================================
04 - Baroque
   

04. Baroque

「彼女こそ…私のエリスなのだろうか…」
“只有她…才是我的Alice吧…”

主よ、私は人間を殺めました。
主啊,我杀了人。
私は、この手で大切な女性を殺めました。
是我,亲手杀死了那对我无比重要的女性。

思えば私は、幼い時分より酷く臆病な性格でした。
回想起来,我从小就相当地胆怯。
他人というものが、私には何だかとても恐ろしく思えたのです。
所谓的“别人”,对我来说总是无比可怕的存在。

私が認識している世界と、他人が認識している世界。
我认知中的世界和、别人认知中的世界。
私が感じている感覚と、他人が感じている感覚。
我感受到的感觉和、别人感受到的感觉。
『違う』ということは、私にとって耐え難い恐怖でした。
对于这种“不同”,我感到难以忍受的恐怖。
それがいづれ『拒絶』に繋がるということを、無意識の内に知っていたからです。
而那就和“拒绝”联系到了一起,我在冥冥之中也能明白。

楽しそうな会話の輪にさえ、加わることは恐ろしく思えました。
即使是快乐的对话,我也害怕加入其中。
私には判らなかったのです、他人に合わせる為の笑い方が。
我完全不能理解,别人为了互相接近而露出的笑容。
いっそ空気にならたら素敵なのにと、いつも口を閉ざしていました。
干脆变成像空气那样就好了,我一直紧闭双唇。
そんな私に初めて声を掛けてくれたのが、彼女だったのです。
而第一次与那样的我搭话的,就是她。

美しい少女でした、優しい少女でした。
美丽的少女(人),温柔的少女(人)。
月のように柔らかな微笑みが、印象的な少女でした。
有着如月儿般柔雅的微笑,令人印象深刻的少女。
最初こそ戸惑いはしましたが、私はすぐに彼女が好きになりました。
在起初的迷惑之后,我很快就喜欢上了她。
私は彼女との長い交わりの中から、多くを学びました。
我在与她长久的交往之中,学到了许多东西。

『違う』ということは『個性』であり、『他人』という存在を『認める』ということ。
所谓的“不同”其实只是“个性”,是人借以“认同”所谓“别人”的东西。
大切なのは『同一であること』ではなく、お互いを『理解し合うこと』なのだと。
重要的并不是“相同的事物”,而是人与人之间的“互相理解”。

しかし、ある一点において、私と彼女は『違い過ぎて』いたのです。
不过,只有一点,我是和她“完全不同”的。

狂おしい愛欲の焔が、身を灼く苦しみを知りました。
疯狂的爱欲之火将我焚烧烤炙,痛苦万分。
もう自分ではどうする事も出来ない程、私は『彼女を愛してしまっていた』のです。
我完全无法自已,因为我“已经爱上了她”。
私は勇気を振り絞り、想いの全てを告白しました。
我鼓足全身勇气,把情感全部向她告白。
那时她说出的话语,是何等地悲哀。
しかし、私の想いは彼女に『拒絶』されてしまいました。
然而,我的感情却被她“拒绝”了。
その時の彼女の言葉は、とても哀しいものでした。
那时她说出的话语,是何等地悲哀。
その決定的な『違い』は、到底『解り合えない』と知りました。
我明白了,那决定性的“不同”,终究还是“无法理解”。

そこから先の記憶は、不思議と客観的なものでした。
到此为止的记忆,很不可思议地,都十分客观。
泣きながら逃げてゆく彼女を、私が追い駆けていました。
她哭着逃开,而我则追了过去。
縺れ合うように石畳を転がる、《性的倒錯性歪曲》の乙女達。
仿佛纠结缠绕一般从石阶上滚落,“性别倒错扭曲”(Baroque)的女郎们。
愛を呪いながら、石段を転がり落ちてゆきました……。
一边诅咒着爱情,一边从石阶上滚落……。

この歪な心は、この歪な貝殻は、
这扭曲的心灵,这扭曲的贝壳,
私の紅い真珠は歪んでいるのでしょうか?
我的红色珍珠也已经扭曲了吗?

誰も赦しが欲しくて告白している訳ではないのです。
我的告解并不指望得到谁的宽赦。
この罪こそが、私と彼女を繋ぐ絆なのですから。
这罪孽的缘由,就是我和她之间的羁绊相牵。
この罪だけは、神にさえも赦させはしない……。
这罪孽的深重,即使是神也无法宽赦吧……。

“那么,就让我来宽赦你吧……”
扭曲珍珠般的女郎,在扭曲之日逝去……(Baroque Vierge, Baroque zi le fine...)

──激しい雷鳴 浮かび上がる人影
——巨响的雷鸣 浮现的人影
いつの間にか祭壇の奥に『仮面の男』が立っていた──
不知何时在祭坛之后站立着“假面的男人”——

=========================================
=========================================
05 - エルの肖像
   

05. EL的肖像

白い結晶の宝石は 風を纏って踊る
白色结晶的宝石 被风包裹着舞跃
樹氷の円舞曲 遠く朽ちた楽園
雾凇之圆舞曲 遥远的湮灭的乐园

黒い瞳孔の少年は 風を掃って通る
黑色瞳孔(眼眸)的少年 被风吹开的
樹氷の並木道 深い森の廃屋
雾凇的林荫道 深深森林中的废屋
少年が見つけた 少女の肖像画
少年见到了 少女的肖像画
「彼」は病的に白い 「彼女」に恋をしてしまった...
“他”对那病态的白皙的 “她”一见倾心…

幼い筆跡の署名 妙に歪な題名は
稚拙地写下的名字(Sign) 奇怪地扭曲的标题(Title)是
【最愛の娘エリスの8つの誕生日に...】
《于最爱的女儿Alice的第八个生日…》


退廃へと至る幻想 背徳を紡ぎ続ける恋物語
——陷于极至颓废(Decadence)的幻想 永续编织的悖德的罗曼史(Romance)
痛みを抱く為に生まれてくる 哀しみ
为承负痛苦而诞生 是多么悲哀
第四の地平線──その楽園の名は「ELYSION」
第四道地平线——那乐园名为『ELYSION』

──そして...幾度目かの楽園の扉が開かれる……
——于是…乐园的门扉不知第几次被打开……

やがて少年は彼の<理想>を求めるだろう...
终有一日少年会将他的“理想”(ideaL)寻求吧…
やがて少年は彼の<鍵穴>を見つけるだろう...
终有一日少年会将他的“匙孔”(keyhoLe)寻见吧…
やがて少年は彼の<楽園>を求めるだろう...
终有一日少年会将他的“乐园”(eLysion)寻求吧…
やがて少年は彼の<少女>を見つけるだろう...
终有一日少年会将他的“少女”(girL)寻见吧…

娘もまた母になり 娘を産むのならば
女儿也会成为母亲 然后再生下女儿的话
楽園を失った原罪を 永遠に繰り返す……
使人失去乐园的原罪 就永远往复循环……

始まりの扉と 終わりの扉の狭間で
开始的门扉与 终结的门扉的夹缝之间
惹かれ合う「E」と「A」──愛憎の肖像
相互靠近的『E』(EL)与『A』(ABYSS)——爱憎之肖像

禁断に手を染め 幾度も恋に堕ちてゆく
触碰禁忌之物 多少次地陷入恋爱之中
求め合う「E」と「A」──愛憎の肖像
相互寻求的『E』(EVA)与『A』(ADAM)——爱憎之肖像


やがて少年は♂の為に自らを殺し 少女は♀の為に自らを殺す
终有一日少年会因(男人)而使自己丧生 少女会因(女人)而令自己身亡
時の荒野を彷徨う罪人達は 其処にどんな楽園を築くのだろうか?
在时间的荒野中徘徊的罪人们 在那里会将怎样的乐园建筑?

──幾度となく「E」が魅せる幻影 それは失ったはずの「E」の面影
——不知几次被『E』(Elysion)的幻影魅惑 那是已然失去的『E』(Eden)的面容
嗚呼...その美しき不毛の世界は 幾つの幻想を疾らせてゆくのだろう──
啊…这美丽而荒凉的世界 又有多少幻想在其中奔走——

=========================================
=========================================
06 - Yield
   

06. Yield

「彼女こそ…私のエリスなのだろうか…」
“只有她…才是我的Alice吧…”

一人娘は せっせと種を蒔く
那女儿独自一人 不断播撒着种子
変わらぬ過去に 訪れぬ未来に
为不会改变的过去 为不会来到的未来
不毛な行為と 君は笑うだろうか?
无果的行为 你会嘲笑的吧?
それなら君は 幸せなんだろうね...
这样的你 真的很幸福呢…

根雪の下で春を待つの 夏が過ぎれば実りの秋ね...
在积雪之下将春天等待 夏季过后就是那收获之秋…

成果...収穫...それは果実を産む
长成…收获…它产下了果实(harvest harvest it yields fruits)
最も遅い収穫...それは甘い果実を産む
最迟来的收获…它产下了甜美的果实(la la, latest harvest it yields sweets)

一夜限りの 情事でも構わない
只有一夜的话 温存(梦想)一下也没关系的
それをも女は 永遠に出来るから
那对女孩来说 就算得到永远(永恒)了
不毛な恋と 君は笑うだろうか?
无果的恋情 你会嘲笑的吧?
やっぱり君は 幸せなんだろうね...
果然你还是 真的很幸福呢…

凍える夜は夢を見るの 夏が過ぎれば想いが実る…
在冰冷的夜里看到了梦 夏季过后就是思念的果实…

結果...収穫...それは果実を産む
结果…收获…它产下了果实(harvest harvest it yields fruits)
最も遅い収穫...それは甘い果実を産む
最迟来的收获…它产下了甜美的果实(la la, latest harvest it yields sweets)


...不安定な数字 ...模範的な数式
「3」…不安定的数字 「3-1」…标准的算式
問題となるのは個の性質ではなく 唯...記号としての数量
问题并不在个体的性质 唯有…作为符号的数量而已
世界が安定を求める以上 早くどれか一つを引かなければ...
为了使世界安定下来 赶快把其中的哪个给排除吧…

何故人間は恋をする 相応しい季節に出会えないの?
为什么人们(人)恋爱了 却不能在合适的季节(时候)相遇?
嗚呼...お父さん...お母さん
啊…爸爸(Dad)…妈妈(Mam)


「──それでも私は幸せになりたいのです……」
“——就算这样,我也想得到幸福……”

恋心 甘い果実 真っ赤な果実
恋情 甜美的果实 鲜红的果实(Sweets, lala Sweets, lala 鲜红的Fruits)
もぎ獲れないのなら 刈り取れば良いと...
要是摘不到它的话 就砍下来好了…
恋心 甘い果実 真っ赤な果実
恋情 甜美的果实 鲜红的果实(Sweets, lala Sweets, lala 鲜红的Fruits)
嗚呼...でもそれは首じゃないか……
啊…可那不是脑袋吗……


(La La La La La La La La La La La La La La La
La La La La La La La La La La La La La La La...)


二人の♀ 一人の♂ 一番不幸なのは誰?
两个(女人) 一个(男人) 其中最不幸的是谁?
落ちた果実...転がる音 余剰な数字...引かれる音
落下的果实…滚动的声音 余剩的数字…排除的声响

「3-1+1-2」

──最後に現れたのは「仮面の男」
——最后出现的是“假面的男人”
彼らが消え去った後 荒野に一人取り残されるのは誰──
当他的身影消失之后 荒野上剩下的一人究竟是谁——

=========================================
=========================================
07 - エルの天秤
   

07. EL的天秤

杀人…偷盗…诱拐…黑市…

──悪魔に
——就像向恶魔
魂を売り渡すかのように 金になる事なら何でもやった
出卖了灵魂一般 为了金钱什么都能做
問うべきは手段では無い その男にとって目的こそが全て
不管任何手段 那个男人只为达成目标
切実な現実 彼には金が必要だった...
切实的现实 他需要金钱…

傾き続けてゆく天秤 その左皿が沈み切る前に
一直在倾斜的天秤 那左盘完全落下之前
力づくでも浮き上がらせるだけの金が 右皿には必要だった...
为了使它升起 必须把金钱放在右盘之上…
そして...その夜も天秤は仮面を躍らせる……
就这样……这一夜,假面也在天秤上跃动……

闇を纏うように 夜の静寂を探り 瞳と瞳を見つめ合って
仿佛被黑暗包裹 在寂静的夜中探求 眼与眼互相凝视
夢想的な月灯りに そっと唇重ね 息を潜めた...
梦幻般(Romantic)的月光照耀 轻轻地将双唇重合 屏住呼吸…
慌しく通り過ぎる 追っ手達を遣り過ごし 手と手を取り合って
慌张地逃跑 追逐的人们从身边跑过 手与手互相交握
戯曲的な逃避行に 酔った二つの人生 愛に捧げた...
戏剧性(Dramatic)的逃亡之旅 迷醉的两个人生(生命) 把爱献上…


再见了吧…(权力走狗手中方便的一张牌)
再见了吧…(卖掉女儿来买得至尊的位置)

身分違いの恋 許されないと知っても ♂と♀は惹かれ合った
身份不符的恋爱 明知不会被允许 (男)与(女)仍然互相追求
嗜虐的な貴族主義を 蹴って檻を抜け出す 嗚呼それは悲劇...
嗜虐(Sadistic)的贵族主义被踢开 从牢笼中逃脱 啊,那样的悲剧…
運命の遊戯盤の上で 支配力を求めて 生と死は奪い合った
在命运的游戏盘(Board)上 寻求支配的力量 生与死互相争斗
徹底的な追悼劇を 笑う事こそ人生 嗚呼むしろ喜劇...
激烈(Drastic)的悼亡剧 可笑的正是人生 啊,毋宁说是喜剧…


再见了吧…(用金币就能让人背叛的世界)
再见了吧…(是外人就能恣意压榨的惨况)

楽園への旅路 自由への船出 逃走の果てに辿りついた岸辺
通向乐园的旅程 通向自由的航行 逃亡的尽头走到了岸边
船頭に扮した男が指を鳴らすと 黒衣の影が船を取り囲んだ……
扮成船夫的男人一打响指 黑衣的影子将船团团围住……

“回去的船资不用担心,已经预付了足够的数目。不过至于他,就在这里再见了吧”
“真可惜啊…”

「娘さえ無事に戻るならばそれで良い 使用人の方など殺しても構わんわ」
“只要女儿能平安回来就好,佣人(男的)宰了就宰了也没关系”
一度も眼を合わせずに伯爵はそう言った... 金貨の詰まった袋が机叩いた...
伯爵眼都不眨地这么说道… 装满金币(Coin)的口袋扔在了桌子(Table)上…

いつも人間は何も知らない方が幸福だろうに
无论何时人类(人)还是一无所知才比较幸福
けれど他人を求める限り全てを知りたがる
但却总向别人(人)尽可能地将全部事情问询
──何故破滅へと歩みだす?
——为何要走上毁灭的道路?

華やかな婚礼 幸せな花嫁 運命の女神はどんな脚本を好むのか...
盛大的婚礼 幸福的新娘 命运女神中意的是怎样的脚本(Scenario)呢…
虚構の婚礼 消えた花嫁 破滅の女神はどんな綻びも見逃さない...
矫饰的婚礼 消失的新娘 毁灭女神无论怎样的破绽都不会放过…

嗚呼...燃えるように背中が熱い その男が伸ばした手の先には何かが刺さっていた
啊…背后如火烧似地灼热 那男人伸手摸到了刺中他的什么东西
嗚呼...緋く染まった手を見つめながら 仮面の男は緩やかに崩れ落ちてゆく...
啊…看着手完全染成红色 假面的男人缓缓瘫倒在地上…
嗚呼...その背後には娘が立っていた 凄まじい形相で地に臥せた男を凝視していた
啊…在他背后站着那女儿 凝视着凄惨地横卧在地上的男人
嗚呼...一歩後ずさり何か叫びながら 深まりゆく闇の彼方へ走り去ってゆく...
啊…她倒退一步,一边呼喊着什么 一边跑向愈发深邃的黑暗中…

──徐々に薄れゆく意識の水底で 錆付いた鍵を掴もうと足掻き続ける
——渐渐模糊的感觉 沉入意识的水底 却还抓住生锈的钥匙挣扎
扉は目の前にある 急がなければ もうすぐ もうすぐ約束した娘の──
门就在眼前了 不快点的话 再一点 再一点就能把女儿的约定——

=========================================
=========================================
08 - Sacrifice
   

08. Sacrifice

「彼女こそ…私のエリスなのだろうか…」
“只有她…才是我的Alice吧…”

(Sacrifice, Sacrifice, ah... Sacrifice, Sacrifice, ah...)

無邪気な笑顔が 愛らしい妹は
天真无邪的笑脸 我亲爱的妹妹啊
神に愛されたから 生まれつき幸福だった
她是被神所爱 只为幸福而生
一人では何も 出来ない可愛い天使
自己一个人 就什么都做不了的可爱天使
誰からも愛される 彼女が妬ましかった
无论谁都会去爱 我开始嫉妒那样的她

器量の悪い私を 憐れみないでよ…
器量狭窄的我 没有必要怜悯…

「──惨めな思いにさせる 妹なんて死んじゃえば良いのに...
“——我屈辱地想着,妹妹(那孩子)还不如当初就死了的好…”


(Sacrifice, Sacrifice, ah... Sacrifice, Sacrifice, ah...)

あくる日妹は 高熱を出して寝込んだ
第二天妹妹 就发着高烧躺在床上
ごめんなさい神様 あの願いは嘘なんです
对不起神啊 那祈求只是我胡说的
懺悔が届いたのか やがて熱は下がった
忏悔被听见了吗 终于热度下去了
けれど今度は母が 病の淵に倒れた
可接下来是妈妈 被病魔缠到身上

母が今際の時に遺した言葉は…
妈妈在临终之时留下的话…
「──妹は他人とは違うから お姉ちゃんが助けてあげてね...
“——妹妹(那孩子)和别人不一样,当姐姐的(你)要注意照顾她…”

(Sacrifice, Sacrifice, ah... Sacrifice, Sacrifice, ah...)

母が亡くなって 暮らしにも変化が訪れ
妈妈已经不在了 生活发生了变化
生きる為に私は 朝な夕な働いた
为了生计的我 从早到晚都在劳作
村の男達は 優しくしてくれたけど
村子里的男人们 还是那样地温和
村の女達は 次第に冷たくなっていった
可村里的女人们 却愈发地冷淡了

貧しい暮らしだったけど 温もりがあった…
日子虽然清苦 却有浓浓的温情在啊
「──肩を寄せ合い生きてた それなりに幸福だった...」
“——相依为命地生活,这就是幸福了吧…”
それなのにどうして...こんな残酷な仕打ちを...教えて神様!
可是为什么…会有这么残酷的事呢…告诉我,神啊!
妹が授かった子は 主が遣わし給うた 神の御子ではないのでしょうか?
妹妹(那孩子)得赐的 难道不是主送来的 神之御子吗?


──妹が子供を身篭っていることが発覚した夜
——妹妹怀孕的事情被发现的夜里
村の男達は互いに顔を見合わせ口を噤んだ
村里的男人们互相交换着眼色噤口不言
重い静寂を引き裂いたのは耳を疑う様な派手な打音
我听错了吗?打破沉重静寂的是一声脆响
仕立屋の若女将が妹の頬を張り飛ばした音…
成衣店的女店主抽在妹妹脸上的耳光声……

偷腥的猫…还说什么“可怜的孩子”…这么照顾她…忘恩负义…

──断片的な記憶...断罪的な罵倒...
断片的记忆…判罪的骂声…
嗚呼...この女は何を喚いているんだろう? 気持ち悪い
啊…这女的(人)在喊什么?真讨厌
ぐらりと世界が揺れ 私は弾け飛ぶように若女将に掴み掛かっていた…
世界剧烈地摇晃着 女店主紧紧揪住我,像是要把我扔出去一样…

緋く染まった視界 苦い土と錆びの味 頭上を飛び交う口論 神父様の怒声
被染成红色的视野 苦涩的泥土和生锈的味道 在头顶起伏的讨论 神父大人愤怒的声音

纯洁被…与恶魔的契约…灾祸之子种…圣母玛利亚…谁都应是加百列…用火焚烧…
“啊…恶魔不正是你们这些家伙吗!”

──そして...妹は最期に「ありがとう」と言った...
——然而…妹妹最后只说了一句“谢谢”而已…

心無い言葉 心無い仕打ちが どれ程あの娘を傷付けただろう
无情的话语 无情的作为 她受到的伤害难以言表
それでも全てを...優しい娘だから...全てを赦すのでしょうね...
可即使这样却全都…她真是温柔…全都宽赦了呢…


“但是,我却绝对不会宽赦啊…”
“这个世界毕竟,只是乐园的代用品不是吗。罪孽深重的家伙们全都,一起归于灰烬就好了!”

──はだしの娘 凍りつくような微笑を浮かべ
——赤脚的女孩 脸上浮现出寒冰一般的微笑
揺らめく焔 その闇の向こうに『仮面の男』を見ていた
摇荡的火光 “假面的男人”在它烧灼的黑暗中现出身形——

=========================================
=========================================
09 - エルの絵本【笛吹き男とパレード】
   

09. EL的画册 【吹笛人与游行队】

そのパレードは何処からやって来たのだろうか
那游行的队伍是从何处而来呢……

あぁそのパレードは何処までも続いてゆく
啊…这游行不管到哪里都在前进着…

(La La La La La La La La La La La La La La La
La La La La La La La La La...)


おぉ友よ、罪もなき囚人達よ
“哦,朋友啊!无罪的囚犯们啊
我等はこの世界という鎖から解き放たれた
我们已从这名为世界的枷锁中解放。
来るものは拒まないが去るものは決して許さない
想来的决不拒绝,想走的决不宽赦。
黄昏の咎に墜ちる 楽園パレードへようこそ
黄昏之葬列…欢迎来到乐园的游行!”

パレードは何処までも続いてゆく
游行不管到哪里都在前进着 →
世界の果てを目指して
 走向世界的尽头
先頭で仮面の男が笛を吹く
前面那假面的男人吹着笛子 →
沈む夕日に背を受けて
 背向沉没的夕阳
(La La)
パレードは何処までも続いてゆく
游行不管到哪里都在前进着 →
世界の果てを目指して
 走向世界的尽头
男の肩に座った少女が歌う
少女正坐在男人的肩上歌唱 →
その笛の音に合わせて
 应和这笛子的声响


心に深い傷を負った者にとって抗えない魔性の音
那是心中刻下深深伤迹的人 绝对无法抗拒的魔性之音…


やぁ友よ幸薄き隣人達よ
“哟,朋友啊!不幸的邻居们啊
我等はこの世界という鎖から解き放たれた
我们已从这名为世界的枷锁中解放。
来るものは拒まないが去るものは決して許さない
想来的决不拒绝,想走的决不宽赦。
仮初の終焉 楽園パレードへようこそ
暂短之终焉…欢迎来到乐园的游行!”

パレードは何処までも続いてゆく
游行不管到哪里都在前进着 →
世界の果てを目指して
 走向世界的尽头
燃えるような赤い髪の女が踊る
红发的女人像火焰一样舞动 →
沈む夕日を背を受けて
 背向沉没的夕阳
(La La)
パレードは何処までも続いてゆく
游行不管到哪里都在前进着 →
世界の果てを目指して
 走向世界的尽头
黒い首吊りピエロのタトゥーが笑う
“可厌的”(黑色)小丑(Pierrot)被绞首的纹身(Tattoo)在笑 →
彼の笛の音に合わせて
 应和那笛子的声响


心に深い闇を被った者にとって逆らえない魔性の音
那是心中养育深深黑暗的人 绝对无法违逆的魔性之音…


笛の音に誘われ一人また一人列に並んでゆく
笛子(相逢)的声音诱惑 人们一个个走进队伍之中
やがてそのパレードは 夕日を遮って地平線を埋め尽くす
很快这游行的行列 就遮住夕阳,将地平线全部覆满…



例えば、箱舟を信じた少女…
譬如那信从着方舟的少女……
例えば、歪んだ真珠の乙女…
譬如那扭曲珍珠般的女郎……
例えば、収穫を誤った娘…
譬如那错误地收获的女儿……
例えば、妹を犠牲にされた姉…
譬如那妹妹被牺牲的姐姐……
例えば、星屑に踊らされた女…
譬如那为星尘操纵的女人……
誰も仮面の男Abyssからは逃げられない
没有人能从假面的男人ABYSS那里逃脱……

ごきげんよう、可哀想なお嬢ちゃん
“日安,可怜的小姐啊。
楽園パレードへようこそ
欢迎来到乐园的游行!”

笛の音を操って一人また一人列に加えてゆく
笛子(相逢)的声音操纵 人们一个个加入队伍之中
やがてそのパレードは夕日を裏切って地平線を焼き尽くす
很快这游行的行列 就背叛夕阳,走向地平线的边缘……

(La La La La La La La La La La La La La La La
La La La La La La La La La...)


嗚呼…そのパレードは何処までも続いてゆく…
啊…这游行不管到哪里都在前进着…

そのパレードは何処へ向って行くのだろうか…
那游行的队伍将要向何处而往……

=========================================
=========================================
10 - StarDust
   

10. StarDust

「彼女こそ…私のエリスなのだろうか…」
“只有她…才是我的Alice吧…”

お揃いね私達 これでお揃いねあぁ幸せ…
在一起的我们 这样就在一起了呢 啊,幸福……

Lulululu... StarDust

女は物言わぬ 可愛いだけの<お人形>じゃないわ
别把女人当物品 她并不是可爱的“玩偶”(Doll)啊
愛しい貴方解って?
——亲爱的你能明白吗?

ちっぽけな自尊心 満たすための道具じゃないわ
那一丁点的自尊(东西) 也不是让你满足它的道具啊
月夜の<別人格>は勝手
——月夜中的“另一重人格”(Another)随意而行?

首を締めれば 締まるに決まってるじゃない
虽然勒住脖子 却不能下决心绞紧
<月>が貴方を狂わせたの?
——月亮(Luna)会使你迷狂吗?

だってしょうがないじゃない 愛してしまったんだもの
这也没有办法 人家已经爱上他了嘛
<星>が私を 狂わせたのは何故?
——星星(Stella)为何使我迷狂如此?

真っ赤な衣装 真っ赤な洋靴
真っ赤な口紅 真っ赤な薔薇
赤红色的衣服(Dress) 赤红色的洋鞋(Heel)
赤红色的口红(Rouge) 赤红色的薔薇(Rose)

すれ違う男達 誰もが振り返る
擦肩而过的男人们 无不回头张望……
左手には花束 右手には約束を
左手里拿着花束 右手里抓着约定
疾りだした衝動は もう止まらない
疾跃的冲动 已不可阻止…

お揃いね私達 これでお揃いね あぁ幸せ
在一起的我们 这样就在一起了呢 啊,幸福……
あなたの白い衣装も 今は鮮やかな深紅
你那白色的衣服(Shirt) 现在已是鲜艳的深红(Scarlet)
お揃いね私達 これでお揃いね あぁ幸せ
在一起的我们 这样就在一起了呢 啊,幸福……


屑でも構わないわ いつか星になれるなら
“…即使是尘土也没有关系,总有一天它也会成为星星
輝いてる? ねぇ私輝いてる?
闪闪发亮吧?看…我是多么闪耀啊?”

綺麗な星空で それは艶やかな女のため息
“美丽的星空”…那是女人娇艳的吐息
君の方が綺麗だよ それは甘い男の囁き
“比不上你的美丽”…那是男人甜蜜的低语
夜空を見上げる恋人達 ありふれた風景
仰望夜空的恋人们 是常见的风景
繰り返される恋模様  ほんの些細な事
循环往复的恋爱的模样 是细微的事情

そんな気まぐれな一時を 永遠だと信じたりして
将那样变化无常的时光 当作永恒相信
そんな不確かなものを 運命だと信じたりして
将那样无从确知的事物 当作命运相信
泣いたり 笑ったり 愛したり 憎んだりして
哭泣 欢笑 爱恋 憎恨
そのつかの間 遥か過去の光に思いをはせたりして
在那夹缝之中 从遥远过去而来的思念之光在飞驰

あの星々はもう滅んでしまっているのだろうか
那些星星是永远不会毁灭的吗?
それとも今もまだ滅びに向かって輝き続けているのだろうか
还是说,它们现在已经毁灭,只是发出的光辉还在持续呢?
光年という名の途方もない尺度の前では
在连光年都无法计算的遥远的尺度之前
人の一生など刹那の幻に過ぎないのかも知れない
人的一生只是刹那之中的虚幻也说不定…

そんな些細なこと されど偶然とはいえ
——就是那细微的事情 该说是巧合吧
嗚呼...偶然とはいえ彼女は見てしまった
啊…所谓巧合地,让她看到了
お揃いの白い服を着て幸せそうに寄り添い歩く
穿着白色衣服的两人在一起幸福地漫步
彼と見知らぬ女の姿を
他和从未见过的女人的身形…

お揃いね私達 これでお揃いね あぁ幸せ
在一起的我们 这样就在一起了呢 啊,幸福……
あなたの白い衣装も 今は…
你那白色的衣服(Shirt) 现在——


なぜ… なぜなの… なぜなのよーー!!
“为什…为什么…为什么啊——!!”

酸素に触れた赤は やがて黒に近づき示す
与氧气接触的红色 迅速变黑
二人はもう永遠に 一つには なれないという事実を
两人已永远地 不能走在一起的事实…

凍てついた銀琉璃の星々 燃え上がる滅びの煌きよ
冰冻的银琉璃般的群星 仿佛要燃烧殆尽般地煌煌闪亮
失くした楽園の夢を見る 私を導け<星屑の幻灯>
在梦里看见失去的乐园 引导着我的“星尘之幻象”(The Light of StarDust)


思い出を過去の光として埋葬できない限り
——只要过去的思念之光还没有被埋葬
孤独な亡霊は荒野を彷徨い続けるだろう
那孤独的亡灵就将永远在荒野之中彷徨
女の手は悲しいほどに短く星屑には届かない
女人的手悲哀而无望地伸向遥远的星尘
嗚呼...その手を握り返したのは「仮面の男」だった
啊……握住了那只手的是“假面的男人”——

=========================================
=========================================
11 - エルの楽園[→ side:A →]
   

11. EL的乐园 [→ Side:A →]

誰かの呼ぶ声が聞こえた 少女はそれで目を覚ます
听到了什么人的呼唤 少女睁开眼睛
心地よい嵐に抱かれて 澄んだ空へと舞い上がる
被舒畅的风包裹 飞翔在澄净的天空

誰かがね...泣いているの...
是谁在…哭泣呢…


それは気の所為かしら?
是我自己听错了吗?
(对,是你听错了哦)
もう...そういうことじゃないわ
不……不是那样的事
(那,是风的缘故吗)
楽園で泣くはずないわ
在乐园中应没有泪水
(对,所以不要哭了)
だって楽園なんだもの
因为这样才是乐园啊
(因为这就是乐园啊)

何処かでね...泣いているの...
在哪里…哭泣呢…

悲しみも苦しみも?
无论悲哀还是痛苦?
(对,在这里都没有)
幸せ満ち溢れる世界?
世界中满溢着幸福?
(对,乐园就是这样)
楽園で泣くはずないわ
在乐园中应没有泪水
(对,所以不要哭了)
だって楽園なんだもの
因为这样才是乐园啊
(因为这才是乐园啊)

本当はね...知っているの...
其实呢…我是知道的…


第四道地平线 那乐园的真实是…

空は荒れ 木々は枯れて 花は崩れ朽ち果て
荒凉的天空 枯萎的森林 花儿衰朽败落
腐敗した大地が 闇の底へと堕ちてゆく...
腐坏的大地 堕向黑暗深渊的最深处……
エルは生まれ エルは痛み エルは望みの果て
那是EL的诞生 EL的痛苦 EL希望的结果
安らぎの眠りを求め 笑顔で堕ちてゆく...
将安乐的睡眠寻求 带着笑容堕向其中…


"Ark"
托付给方舟的那些愿望…

"Baroque"
扭曲的恋爱互相寻求融合…

"Yield"
一直在盼望着理想的收获…

"Sacrifice"
不管多大的牺牲都盲目地献奉…

"StarDust"
终于能够向星尘伸出自己的手…

挟み込まれた四つの<楽園>に惑わされずに
不为自己周边的那四个“乐园”(EL)迷惑
垂直に堕ちれば其処は<奈落>
径直堕下的地方,那里是“深渊”(ABYSS)

何処から来て 何処へ逝くの 全ては誰の幻想?
从何处而来 消逝在何处 一切都是谁的幻想(梦)?
差し出された手に 気付かないままに堕ちてゆく...
完全没有注意伸过来的手 就这样堕入…
エルは倦まれ エルは悼み エルは望みの涯
那是EL的疲倦 EL的悼念 EL希求的终末
安らぎの眠りを求め 笑顔で堕ちてゆく...
将安乐的睡眠寻求 带着笑容堕向其中…


──退廃へと至る幻想 背徳を紡ぎ続ける恋物語
——陷于极至颓废(Decadence)的幻想 永续编织的悖德的罗曼史(Romance)
痛みを抱く為に生まれてくる 哀しみ
为承负痛苦而诞生 是多么悲哀
幾度となく開かれる扉 第四の地平線──
一次次低鸣着被打开的门扉 第四道地平线——
その楽園の名は「ELYSION」またの名を「ABYSS」──
那乐园名为『ELYSION』又名『ABYSS』——

=========================================
=========================================
44 - bonustrack
   

44. Ending

(*:全CD的音轨共45首,“44”为隐藏的数字。音轨12到43各无声5秒,音轨45无声7秒)

“我回来了…EL……”
“欢迎回来…爸爸…”

只要那男人的妄念 仍永远孕育下去
被称为故事的历史 就会无尽循环吧

——陷于极至颓废(Decadence)的幻想 永续编织的悖德的罗曼史(Romance)
承负着痛苦而诞生的 第四道地平线——
它真实的名字是——

===========================================
由一条假面男主线穿插出5个女子的悲惨故事。。。
Ark Baroque Yield Sacrifice Stardust ----> ABYSS
背景似乎有多种解读,有兴趣可以查一下

虽然这张CD已经有几年了,但时不时回味一下仍然很有感觉。听多了大概有洗脑效果吧。。。:~)

2009年3月

locale的使用总结

locale 是多种 facet 的容器,每种 facet 管理与 locale 相关的一种功能。
facet 除了按名称区别外,更常用的是按 category 来分类。分类情况如下:
 
locale::ctype 类别,包括以下 facet 模板
ctype // 字符分类和转换
codecvt // 字符编码转换
locale::collate 类别,包括以下 facet 模板
collate // 字符串校对
locale::message 类别,包括以下 facet 模板
messages // 从信息目录中获得本地化信息
locale::numeric 类别,包括以下 facet 模板
numpunct // 有关数字和布尔运算表达式中标点符号及格式信息
num_get // 代表数字或布尔值的字符串的解析
num_put   // 代表数字或布尔值的格式化字符串的生成
locale::monetary 类别,包括以下 facet 模板
moneypunct // 货币表达式中的标点符号及格式
money_get   // 代表货币值的字符串的解析
money_put   // 代表货币值的格式化字符串的生成
locale::time 类别,包括以下 facet 模板
time_get // 代表日期和时间的字符串的解析
time_put // 代表日期和时间的格式化字符串的生成
 
使用方法:
locale 对象是不可变的,即在它们的生命周期中,它们的内容不可改变。所包含的 facet 不能进行修改或替换,同时 facet 不能增加或删除。
鉴于以上特性,在使用 locale 时一般都是根据需要生成新的 locale 对象,然后选入IO流中。因此 locale 的构造函数就变得十分多样,方便我们以各种形式构造所需要的locale 对象。
例如,需要 std::wostream 输出中文,我们就需要 locale("chs") 中编码转换相关的功能,但若直接选择 locale("chs"),输出数字时也会进行转换处理,例如将 1234 输出为 "1,234"。为了避免这一转换,就需要保留原 locale("C") 中除了字符相关的其他facet。如下处理即可
locale loc("chs", locale::ctype);
此函数以 global 对应的 locale (一般是 locale("C") ) 初始化 loc 并选择 locale("chs") 的字符相关 facet ,这样我们就可以用 loc 正确输出中文,并保持输出数字时不进行其他处理
其他可参阅 MSDN 中关于 locale 的构造函数说明,解释很详细,用法很简单。
此外,locale 对象还可使用 combine 成员函数 选取其他 locale 中指定 facet 进行组合。总之接口多样,不过也一定程度上增加了对 locale 学习的复杂性。
 
最后,还有更简便的解决方案,即全局 locale 对象。
如果你建立了一个流对象,并且没有规定流使用的 locale 对象,那么流使用全局 locale 对象。如果我们在程序开始处改变全局 locale 对象为我们需要的形式,以后就不需再反复设定每个流的 locale 了。如
locale::global(locale("",locale::ctype));
除了将字符处理部分改为当前系统默认的编码方式外其他不变,这样一般就满足需求了。
再加一条,当流建立后再改变全局locale,则对已建立的流无影响。还有改变 C++ 的全局 locale 对象可能会影响 C 的全局 locale 设定,请不要混用。既然用 locale 对象,就全部采用 C++ 的风格吧
2009年3月

C++ STL IO流 与 Unicode (UTF-16 UTF-8) 的协同工作

凡用到文件读写,输入输出,就得和编码、Unicode 打交道。这系列实验来测试一下 C++ STL 的 IO流 对 ANSI 编码、Unicode 编码的支持特性,看能否找到一个自动识别编码,自动转码的解决方案。从基础开始,一步一步来:
 
平台 Win32 XP sp3 + VS2008. (+ Boost 1.36.0)
 
实验 01:
#include<string>
#include<iostream>
#include<locale>
using namespace std;
 
locale prevloc;
locale loc("chs");
 
string str1("string class");
string str2("汉字与字符");
wstring wstr1(L"wstring class");          //去掉L前缀则编译错误
wstring wstr2(L"汉字与字符");
 
prevloc = cout.imbue(locale(""));
cout<<"Default Locale: "<<prevloc.name()<<endl;
cout<<"System Locale: "<<locale("").name()<<endl;
cout<<"C风格字符串\n"<<L"w-string\n"<<str1<<'\n'<<str2<<'\n'<<endl;
 
prevloc = wcout.imbue(loc);   //若去掉此句,则wstr2无法正常输出
wcout<<"Default Locale: "<<prevloc.name().c_str()<<endl;    //若不加 .c_str() 则编译错误
wcout<<"chs Locale Name: "<<loc.name().c_str()<<endl;
wcout<<"C-string\n"<<"C风格字符串\n"<<L"宽字符串\n"<<wstr1<<'\n'<<wstr2<<'\n'<<endl;
 
结论:
        1.cout 与 string 配合使用,wcout 与 wstring 配合使用,交错则编译错误(类型问题)
        2.wstring 初始化时需用 L"xxx" 的宽字符形式,同样 string 初始化时不能加 L 前缀
        3.默认locale ("C")下 cout 可以正常输出 C风格字符串与std::string类型,包括汉字也能正常显示
    但对 L"xxx" 宽字符串无能为力
          默认locale ("C")下 wcout 不能输出中文,包括C风格字符串、宽字符串与std::wstring
    设定系统 locale ("chs")后,正常输出宽字符串与std::wstring,但 C风格字符串 中的汉字无法显示
 
        总之,string cout "C-style 字符串" 自成体系
                  wstring wcout L"宽字符串" 自成体系,但 wcout 要选择 locale 后才能正常输出中文。
 
实验 02:
cout.imbue(locale(""));
wcout.imbue(locale(""));
 
string  str3 ( "abc汉字");
wstring wstr3(L"abc汉字");
 
cout<<"str1 length: "<<str1.length()<<'\n'; // 12
cout<<"str2 length: "<<str2.length()<<'\n'; // 10
cout<<"str3 length: "<<str3.length()<<'\n'; // 7
cout<<str2[0]<<' '<<str2[1]<<'\n'// 输出:?
cout<<endl;
wcout<<L"wstr1 length: "<<wstr1.length()<<'\n'; // 13
wcout<<L"wstr2 length: "<<wstr2.length()<<'\n'; // 5
wcout<<L"wstr3 length: "<<wstr3.length()<<'\n'; // 5
wcout<<wstr2[0]<<' '<<wstr2[1]<<'\n';   // 输出:汉 字
 
结论:
        4.std::string 内部以 char 类型储存字符,当有汉字时以双字节存储,此时 length() 给出
    字符串所占字节数而不是字符数
          std::wstring 内部以 wchar_t 类型存储字符,字母汉字统一都是双字节,此时 length()
    给出是正确的字符数。
        5.当std::string中有汉字存在时,通过下标访问不能得到正确的字符。这是显而易见的,
    一方面字符宽度不统一无法随机访问,另一方面 std::string[] 返回 char 类型。std::wstring
    不存在此问题。
 
实验 03:
// test.txt 为 ANSI 编码(GB2312),内容为以上 str1 ~ str3 的3行。
#include<fstream>
 
string str;
wstring wstr;
 
ifstream fin("test.txt");
//fin.imbue(locale(""));
while(fin>>str)
    cout<<str<<'\n';
fin.close();
 
wifstream wfin("test.txt");
//wfin.imbue(locale(""));
//wfin.imbue(locale(".936"));
while(wfin>>wstr)
    wcout<<wstr<<'\n';
wfin.close();
 
结论:
       6.std::ifstream 读取 ANSI 编码正常,std::wifstream 读取 ANSI 编码错误...默认 locale("C") 不能识别中文字符
          std::wifstream 设置 imbue(locale("")) 或 locale(".936") 后正常读取。936 为 GB2312 的代码页。
 
 实验 04:
 test.txt 为 Shift-JIS 编码,内容为
 うみねこのなく頃に

 程序代码同实验3
 ifstream 输出为
 偆傒偹偙偺側偔崰偵
 wifstream 设定 imbue(locale("")) 后输出相同
 
结论:
       7.显而易见的,其他地区的编码无法正确识别。这也是很多日本游戏和文本文件运行
    或读取时产生乱码的原因。
 
 实验 05:
 test.txt 为 Shift-JIS 编码,内容同上
 ifstream 与 wifstream 都添加 imbue(locale("jpn")) 或 locale(".932")
932 为 Shift-JIS 的代码页
 输出为:
 偆傒偹偙偺側偔崰偵
 うみねこのなく頃に
 
 
结论:
       8.这里可以看出一个显著性差异。wifstream 在读取时按照 Shift-JIS 编码将其转换为
    Unicode 储存,在 wcout 输出时又按照 ANSI (GB2312) 转换,其结果是 —— 正确显示
    了其他地区编码的字符。而 ifstream 与 cout 则缺少那两步转换,结果与上例相同
    以后的实验将不再考虑 ifstream 而只实验 wifstream。
 
 实验 06:
 test.txt 存为 UTF-16 编码(Win32 默认的 little endian),内容同上。
 wifstream 设定为 imbue(locale(".1200"))
 1200 为 UTF-16 的 code page
 
 结果,运行出错...发现是 imbue(locale(".1200")); 这句的问题
 试着将 ".1200" 改为 ".936" 则运行正常,输出乱码。(936是 GB2312 的代码页)
 翻 MSDN 时在 Code Page 那页1200 UTF-16 后面发现一行小字:
 "available only to managed applications"...郁闷
 看来用 locale 转Unicode的想法到此结束了?记得 STL 书中貌似说过,locale 的名
 字在各平台上是不统一的,因为关系到各平台的支持问题。这样的话,要么自己写
 代码,要么就只好用 API 显式转换了:MultiByteToWideChar
 另外,在 setlocale 函数说明中也写到,UTF-8 和 UTF-7 等每字符有可能大于2字节
 的编码不被支持,所以 UTF-8 也只能用 MultiByteToWideChar 转咯...
 目前大概只能得出结论 C++ STL locale 在 Win32 平台上支持不完善吧
 
 实验 07: 用 API 重写读文件部分代码
#include<windows.h>

HANDLE hFile;
if(INVALID_HANDLE_VALUE != (hFile = CreateFileW(L"test.txt",
        GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL))){

    int iFileLength, iUniTest, i;
    iFileLength = GetFileSize(hFile,NULL);
    char *pBuffer, *pText;
    pBuffer = new char[iFileLength+2];
    DWORD dwBytesRead;

    ReadFile(hFile,pBuffer,iFileLength,&dwBytesRead,NULL);
    CloseHandle(hFile);
    pBuffer[iFileLength] = '\0';
    pBuffer[iFileLength + 1] = '\0';
 
    iUniTest = IS_TEXT_UNICODE_SIGNATURE | IS_TEXT_UNICODE_REVERSE_SIGNATURE;
    if(IsTextUnicode(pBuffer,iFileLength,&iUniTest)){
        pText = pBuffer + 2;
        iFileLength -= 2;
        if(iUniTest & IS_TEXT_UNICODE_REVERSE_SIGNATURE){
            for(i = 0;i < iFileLength; i+=2)
                swap(pText[i],pText[i+1]);
        }
        wstr = (wchar_t*)(pBuffer+2);
    }
    delete [] pBuffer;
    wcout<<wstr<<'\n';
}
 
        输出正确。以上程序段自动识别 Unicode 编码文件开头的 0xFFFE 标记判断是 Little Endian 还是
    Big Endian 并做相应转换。但是代码量较大,且与 C++ 的 IO流 很不搭调...
 
结论:
       9.可以看到,只是把输入内容去掉UTF-16开头的0xFFFE,直接把内存指针改为
    wchar_t* 后 std::wstring 即可正确识别,说明程序中的宽字符存储格式实际上用的就是
    UTF-16 little endian
 
 实验 08:
 不死心又去翻了 boost 库,发现 codecvt_null 这个好东西,看下实现是把文件存储内容
 按照 wchar_t 为单位直接读入内存不做任何转换。这其实不正好是 UTF-16 需要做的么
 以下把 test.txt 存为 UTF-16 little endian 再次实验
#include<boost/archive/codecvt_null.hpp>

wifstream wfin(L"test.txt");
locale utf16(loc, new boost::archive::codecvt_null<wchar_t>);
wfin.imbue(utf16);
while(wfin>>wstr){
    wcout<<wstr<<endl;
}
wfin.close();
 
输出正确。
 
结论:
       10. 看来可以把 codecvt_null 作为 UTF-16 的 codecvt_facet 读入 locale
    来使用,避免使用类似上面 API 那么多代码。
 
 实验 09:
 将 test.txt 存为 UTF-16 Big Endian ,内容不变。程序不变
 
无法输出任何内容。
结论:
       11. wcout 不认识 big endian 的 wchar_t ...
    看来想读取 UTF-16 Big Endian,仅靠 codecvt_null 还不够。稍微翻了一下
    《C++ 输入输出流与本地化》这本书,现在可以考虑写一个自己的 codecvt_facet
    了。有了 codecvt_null 的代码,稍作改动即可用于 UTF-16 big endian。虽说有了
    现在的知识自己写个 utf-16 的codecvt_facet 也可以,但效率大概比不上 boost 里的。
 
代码准备:用类似的方法写出了自己的 codecvt_utf16 和 codecvt_utf16_reverse 两个
codecvt_facet...然后继续实验。自己写的内容放入咱自己的头文件吧:codecvt_utf.h,
内容加入自己的 namespace : tvt
 
 实验 10: 用 codecvt_utf.h 代替 codecvt_null.hpp。用 codecvt_utf16 和
 codecvt_utf16_reverse 实现 little endian 与 big endian 的输入。

wifstream wfin(L"test.txt");
locale utf16(loc,new tvt::codecvt_utf16<wchar_t>);
wfin.imbue(utf16);
while(wfin>>wstr){
    wcout<<wstr<<endl;
}
wfin.close();
///////////////////////////////////////
wifstream wfin(L"test.txt");
locale utf16(loc,new tvt::codecvt_utf16_reverse<wchar_t>);
wfin.imbue(utf16);
while(wfin>>wstr){
    wcout<<wstr<<endl;
}
wfin.close();
 
第一段程序读取 UTF-16 little endian 编码的 text.txt 正确输出
第二段程序读取 UTF-16 big endian 编码的 text.txt 正确输出
 
UTF-16 的转码顺利完成。下面考虑 UTF-8 ,写法类似。在 boost 库中继续寻找,发现
这个东东 boost/detail/utf8_codecvt_facet.hpp 。看下说明,不支持直接使用此文件,这文件
是专门提供其他 boost 组件使用的。仅 include 它的话编译出问题。再寻找到同名的 cpp 文件
后即可看到 do_in do_out 这两个转码关键的虚函数。有了上面 UTF-16 的基础,我们类似可写
出 UTF-8 的转码 codecvt_facet。我给他起名为 codecvt_utf8, 依然加入 codecvt_utf.h 文件。
现在此文件有一两百行了。经试验可正确输入 UTF-8 编码。
 
对应编码有了处理方法后,下一个问题是编码识别。
 
实验 11:
wchar_t wc;
wchar_t buf[2];
wifstream wfin(L"text.txt");
wfin.read(&wc,1);
wfin.read(&buf[0],2);
 
将 wc 和 buf 的内容按2进制或16进制输出。
结论:
       12. wistream.read(buffer,count) 操作每次读入 count 个字节,但将每个字节存入一个
 wchar_t 类型的 buffer[i] 中。其实 buffer 中每个 wchar_t 的高位都字节是 0 ...
 
 实验 12:
 加入判断条件,在 wfin 中自动加入合适的 utf16 facet,使得自动识别并读取
 little endian 和 big endian 编码的文件:

wchar_t buf[2];
wifstream wfin(L"test.txt");
wfin.read(buf,2);

if(buf[0] == wchar_t(0xFF) && buf[1] == wchar_t(0xFE)){
    cout<<"little endian"<<endl;
    wfin.imbue(locale(loc,new tvt::codecvt_utf16<wchar_t>));
}
else if(buf[0] == wchar_t(0xFE) && buf[1] == wchar_t(0xFF)){
    cout<<"big endian"<<endl;
    wfin.imbue(locale(loc,new tvt::codecvt_utf16_reverse<wchar_t>));
}
while(wfin>>wstr){
    wcout<<wstr<<endl;
}
 
对于两种编码的 text.txt 都实现了自动识别并正确读取。输出正确!
 
结论:
       13.UFT-16在传输时几乎都会加上 0xFFFE 等传输标志很容易判断,即使没有, Win32 下
    也有 IsTextUnicode 这 API 用专门方法判断。UTF-8 就很麻烦了,开头不一定都有 BOM 标
    记,与各地区字符集一样都可以用一个或多字节表示一个字符,编码长度不固定,如果是
    很长一段 ASCII 字符,那么用 UTF-8 和 GB2312 编码出来结果一样,就很难分辨
 
代码准备:经过一段时间思考,打算用这种算法。先读取前3字节,若是 BOM 头标记最好。若
不是则排除 UTF-16 ,下面集中力量分辨 UTF-8 与 ANSI 。从头开始寻找第一个 >127 的字节
若此字节内容 < 0xC0 或 >0xEF 则可判断不是 UTF-8 。否则,根据 UTF-8 的规则,在后面1 或
2 字节中看开头两位是不是 10 。若不是则断定不是 UTF-8 ,否则就算得到一个 UTF-8 字符。
如果能够找到 10个 满足条件的 UTF-8 字符就判断为 UTF-8 编码。若未到 10 个即遇到文件结
尾,那么找到 UTF-8 字符数大于 1 即断定为 UTF-8 否则断定为 ANSI ...
用这种方式选择对应转码 facet:
wistrm.imbue(std::locale(wistrm.getloc(), new codecvt_utf8));
 
按以上想法写成函数 int IsStreamUnicode(std::wistream &wistrm); UTF-16 LE 返回1,BE 返回2,
UTF-8 返回3,否则返回 0 (判断为ANSI)
 
实验 13:
 
std::wifstream wfin(L"test.txt");
if(!tvt::IsStreamUnicode(wfin))
    wfin.imbue(loc);
while(wfin>>wstr)
    wcout<<wstr<<endl;
 
 在我试验的各种情况下,均能自动识别 UTF-16 LE UTF-16 BE UTF-8 与 ANSI 编码
 并正确设定转码 locale .
 
 
-------------------------------------------------------------------------------------
8小时后,关于后续实验的补充:
 
使用中发现某些情况下 UTF-16 的读写出现问题,特别是有换行符或某字节中编码刚好
等于控制符时。经过反复测试认定是 读写mode 问题。在读写 Unicode 文件时,
wifstream 与 wofstream 都设定为 ios_base::binary 模式即可。后来又补充了一个添加
BOM 头的小东西。为了使用简便把 utf_16 的 template 也去掉了。最终情形使用起来
像这个样子:
 
#include<iostream>
#include<fstream>
#include<codecvt_utf.h>
using namespace std;
 
wstring wstr;
wcout.imbue(locale(""));
 
// Open the Input and Output Files:
std::wifstream wfin(L"test.txt", ios_base::binary);
std::wofstream wfout(L"testout.txt", ios_base::binary);
 
// Set Output Format and Write BOM tag:
wfout.imbue(locale(locale(""), new tvt::codecvt_utf16));
wfout<<tvt::utf_bom;
 
// Detect the Format of the Input File
if(!tvt::IsStreamUnicode(wfin))
    wfin.imbue(locale(""));
 
// Read and Write
//while(wfin>>wstr){
//    wcout<<wstr<<endl;
//    wfout<<wstr<<endl;
//}
 
// Another way:
while(getline(wfin,wstr)){
    wcout<<wstr<<endl;
    wfout<<wstr<<endl;
}
 
// Close Files:
wfin.close();
wfout.close();
 
读写测试全部通过!
 
感谢 记事本、EditPlus 和 HxDen 的大力支持...

 至此,关于 Unicode 编码和 C++ STL IO流 的协作算是大功告成了吧,呵呵。以后有需要再
在实践中改进

 花了整整一天时间 + 8 小时 = = 还算有价值吧,因为在网上看到很多人都在问且没有结果
 
 ===========分隔线============
 另附:现在来看用 c++ 的 IO stream locale 系列实现转码并不是一个经济的选择,如果用 STLport 的话还好些,用 VC STL 则存在较严重的效率问题:
2009年3月

Python os.walk() 函数中的陷阱

用 VC 写程序多了会留下很多中间文件和辅助文件,浪费不少空间,一个个去清理项目又费时费力,写个脚本吧,以后也好用。
 
然后想当然的写下了这样的语句:
for root, dirs, files in os.walk(path):
    for name in files:  
        if splitext(name)[1] == '.sln':
            print "Cleaning",join(root,splitext(name)[0]),"..."
            dirs = []
            CleanSolution(root,splitext(name)[0])
 
在某文件夹下发现 '.sln' 项目文件则不需再往下层文件夹寻找。根据 os.walk() 函数的解释,在循环中修改 dirs 变量即可控制下层递归访问的文件夹,语句 dirs = [] 把 dirs 置空,则递归访问应到此结束。但是在跟踪执行中发现,虽然 dirs 确实被置空了,os.walk() 的递归访问却并没有终止,仍然依次访问了下层的所有文件夹...为什么呢?
尝试着把 dirs = [] 改为 for dir in dirs: dirs.remove(dir) 后运行得到了理想的结果
但为何 remove() 函数可以,赋值却不行?猛然间想到以前实验的一个例子:
 
例一:
>>> a = [1,2,3]
>>> b = a
>>> b.remove(2)
>>> a
[1, 3]
 
此例很能说明问题,虽然在 b 上执行了 remove(2) 操作,但 a 中的元素也被改动了,说明 b = a 实际上并没有复制 list 中的元素,而只是相当于把一个指针传给了 b,a、b实际都指向同一个 list
 
例二:
>>> a = [1,2,3]
>>> b = a
>>> b = []
>>> a
[1, 2, 3]
 
有了上例的基础再看这个应当很好理解了,b = [] 只相当于把一个空 list 的指针给了 b,a 中的元素自然不受影响
 
例三:
>>> a = [1,2,3]
>>> b = a
>>> b[:] = []
>>> a
[]
 
只是把 b 变成了 b[:],这次 a 中的元素被全部清空
 
最后再次回到一开始的 os.walk() 上,递归中 dirs 实际上是以类似 b = 内部 list 的形式得到了所有子文件夹列表,dirs = [] 就不会有任何结果了。把程序改为
dirs[:] = []
del dirs[:]
即可正确实现。
 
看来还是实践比较有锻炼效果,以前专门注意过 list 的赋值动作,结果这次藏在 os.walk() 函数中依然没有发现,造成了潜在错误…
 
PS. 脚本挺好用,居然腾出了 1GB 多空间
2009年2月

最小环 与 Floyd

Floyd 的 改进写法可以解决最小环问题,时间复杂度依然是 O(n^3),储存结构也是邻接矩阵
 
int mincircle = infinity;
Dist = Graph;

for(int k=0;k<nVertex;++k){
    //新增部分:
    for(int i=0;i<k;++i)
        for(int j=0;j<i;++j)
            mincircle = min(mincircle,Dist[i][j]+Graph[j][k]+Graph[k][i]);
    //通常的 floyd 部分:
    for(int i=0;i<nVertex;++i)
        for(int j=0;j<i;++j){
            int temp = Dist[i][k] + Disk[k][j];
            if(temp < Dist[i][j])
                Dist[i][j] = Dist[j][i] = temp;
        }
}
 
上面是对无向图的情况。
Floyd 算法保证了最外层循环到 k 时所有顶点间已求得以 0…k-1 为中间点的最短路径。一个环至少有3个顶点,设某环编号最大的顶点为 L ,在环中直接与之相连的两个顶点编号分别为 M 和 N (M,N < L),则最大编号为 L 的最小环长度即为 Graph(M,L) + Graph(N,L) + Dist(M,N) ,其中 Dist(M,N) 表示以 0…L-1 号顶点为中间点时的最短路径,刚好符合 Floyd 算法最外层循环到 k=L 时的情况,则此时对 M 和 N 循环所有编号小于 L 的顶点组合即可找到最大编号为 L 的最小环。再经过最外层 k 的循环,即可找到整个图的最小环。
 
若是有向图,只需稍作改动。注意考虑有向图中2顶点即可组成环的情况。