PROB: comehome & fracdec

comehome:赤裸裸的最短路径

出口都给了,求每个点的最短路径,之中再选最短的即可。

数据量很小,只有 52 个点。

我用了好写的 spfa, 调了一点就跑通了。看题解推荐的是更加好写的 floyd 算法,毕竟数据量太小了,节省一点 code 时间还是不错的。

这道题还有一点交代:可能有 x -> x 的路(没有用,我在输入的时候就排除掉了),可能两点有不止一条路(有用的一定是更短的,同样在输入的时候检查掉了),都没什么要考虑的。

最近我写的宏定义越来越多了……不得不说这玩意挺方便的。

AC 代码:

#include <cstdio>
#define N 52
#define Inf (1 << 30)
#define isCap(c) ((c) >= 'A' && (c) <= 'Z')
//52个字母的顺序是,a-z,A-Z
#define idx(c) (isCap(c)? ((c) - 'A' + 26): ((c) - 'a'))
#define chr(n) (char('A' + (n) - 26))

int map[N][N];
int dist[N];

void spfa(){
    bool inq[N] = {0};
    int queue[N * N], end = 1, start = 0;

    dist[idx('Z')] = 0;
    queue[0] = idx('Z');
    inq[idx('Z')] = true;

    int x;
    while(end > start){
        x = queue[start ++]; //出队
        for(int i = 0; i < N; i ++){
            if(i != x && map[i][x] != Inf){ //邻居
                if(dist[x] + map[x][i] < dist[i]){ //更短
                    dist[i] = dist[x] + map[x][i];
                    queue[end ++] = i;
                }
            }
        }
    }
}

int main(){
    FILE *fin = fopen("comehome.in", "r");
    FILE *fout = fopen("comehome.out", "w");

    for(int i = 0; i < N; i ++){
        for(int j = 0; j < N; j ++){
            map[i][j] = Inf;
        }
        map[i][i] = 0;
        dist[i] = Inf;
    }

    int nRoad, d;
    char from, to;
    fscanf(fin, "%d\n", &nRoad);
    for(int i = 0; i < nRoad; i ++){
        fscanf(fin, "%c %c %d \n", &from, &to, &d);
        if(d < map[idx(from)][idx(to)]){
            map[idx(from)][idx(to)] = d;
            map[idx(to)][idx(from)] = d;
        }
    }

    spfa();

    int quickest = 26, minDist = Inf;
    for(int i = 26; i < N - 1; i ++){
        if(dist[i] < minDist){
            minDist = dist[i];
            quickest = i;
        }
    }

    fprintf(fout, "%c %d\n", chr(quickest), minDist);

    return 0;
}

速度:

Compiling...
Compile: OK

Executing...
   Test 1: TEST OK [0.000 secs, 4184 KB]
   Test 2: TEST OK [0.000 secs, 4184 KB]
   Test 3: TEST OK [0.000 secs, 4184 KB]
   Test 4: TEST OK [0.000 secs, 4184 KB]
   Test 5: TEST OK [0.000 secs, 4184 KB]
   Test 6: TEST OK [0.000 secs, 4184 KB]
   Test 7: TEST OK [0.000 secs, 4184 KB]
   Test 8: TEST OK [0.000 secs, 4184 KB]
   Test 9: TEST OK [0.000 secs, 4184 KB]

All tests OK.
YOUR PROGRAM ('comehome') WORKED FIRST TIME!  That's fantastic
-- and a rare thing.  Please accept these special automated
congratulations.

fracdec:好像是玩字符串?

在第 8 个点就超时的代码:

#include <cstdio>
#define N 200010

//整数的位数
int intLen(int n){
    int counter = 0;
    do{
        n /= 10;
        counter ++;
    }while(n != 0);
    return counter;
}

//找到循环节 T[start: end)
void findLoop(char T[], int len, int &start, int &end){
    bool flag = false;
    int k;
    for(end = 1; end < len && !flag; end ++){
        for(start = 0; start < end && !flag; start ++){
            for(k = 0; start + k < end; k ++){
                if(T[start + k] != T[end + k]) break;
            }
            if(start + k == end){
                int tStart, tEnd;
                for(tStart = start, tEnd = end; tEnd < len; tEnd ++, tStart ++){
                    if(T[tStart] != T[tEnd]) break;
                }
                if(tEnd == len) flag = true;
            }
        }
    }
    start --; end --;
}

//格式化输出带循环的小数部分
void printLoopDecimal(FILE *fout, char T[], int start, int end, int count){
    for(int i = 0; i < start; i ++, count ++){
        if(count == 76) {fprintf(fout, "\n"); count = 0;}
        fprintf(fout, "%c", T[i]);
    }

    if(count == 76) {fprintf(fout, "\n"); count = 0;}
    fprintf(fout, "("); count ++;

    for(int i = start; i < end; i ++, count ++){
        if(count == 76) {fprintf(fout, "\n"); count = 0;}
        fprintf(fout, "%c", T[i]);
    }

    if(count == 76) {fprintf(fout, "\n"); count = 0;}
    fprintf(fout, ")\n");
}

int main(){
    FILE *fin = fopen("fracdec.in", "r");
    FILE *fout = fopen("fracdec.out", "w");

    int n, d, integer, len = 0; //分子,分母,整数部分,小数长度
    char decimal[N]; //小数部分
    int count; //记录输出字符数

    fscanf(fin, "%d %d", &n, &d);
    integer = n / d; //分离整数部分
    count = intLen(integer);//计算整数部分位数
    
    n = n % d; //留下的产生小数部分
    while(len < N){ //做除法,直至除尽或者除了N位
        decimal[len ++] = (n * 10 / d) + '0';
        n = (n * 10) % d;
        if(n == 0) break;
    }
    fprintf(fout, "%d.", integer);
    count ++;

    
    if(n == 0){ //是个有限小数
        for(int i = 0; i < len; i ++, count ++){
            if(count == 76) {fprintf(fout, "\n"); count = 0;}
            fprintf(fout, "%c", decimal[i]);
        }
        fprintf(fout, "\n");
    }else{ //是个无限循环小数,寻找循环节
        int start, end;
        findLoop(decimal, N, start, end);
        printLoopDecimal(fout, decimal, start, end, count);
        
        // 用于检查
        // fprintf(fout, "%d %d\n", start, end);
        // for(int i = 0; i < 100; i ++){
        //     fprintf(fout, "%c", decimal[i]);
        // }
    }

    return 0;   
}

其中寻找循环节的函数findLoop(char, int, int&, int&)用的是最最暴力的算法,明显需要改进。

最后的最后我求助 KMP 算法了。滚回去又学了一遍,见今日完成的另一篇文章。整体思路是,用 KMP 的一些东西改进findLoop函数。我惊喜地发现用求 next 数组的方法来做时,循环部分的值会不断增加。于是,这个函数被改成这样:

oid reverse(char *s, int len){
    char t;
    int i;
    for(i = 0; i < len - 1 - i; i ++){
        t = s[i];
        s[i] = s[len - 1 - i];
        s[len - 1 - i] = t;
    }
}

//kmp中的next数组
void buildNext(char *P, int m, int* next){ 
    next[0] = -1;
    int j = 0, t = -1;
    while(j < m){
        if(t < 0 || P[j] == P[t]){
            j ++, t ++;
            next[j] = t;     
        }else{
            t = next[t];
        }
    }
}

//找到循环节 T[start: end)
void findLoop(char T[], int len, int &start, int &end){
    int next[len + 1], lenLoop = 0;
    reverse(T, len);
    buildNext(T, len, next);
    int maxIdx = 0;
    for(int i = 1; i < len + 1; i ++){ //找到next[i]的最大值
        if(next[i] > next[maxIdx]) maxIdx = i;
    }
    lenLoop = maxIdx - next[maxIdx];
    start = len - maxIdx;
    
    reverse(T, len);

    end = start + lenLoop;
    // for(int i = 0; i <= len; i ++){
    //     fprintf(fout, "[%d] = %d\t", i, next[i]);
    // }
}

我基本是用观察的方法猜出来的。对于

小数部分  2 5 8 1 8 1 8 1 8
秩       0 1 2 3 4 5 6 7 8
前后倒置  8 1 8 1 8 1 8 2 5
next数组 -1 0 0 1 2 3 4 5 0

这个数组倒过来,肯定是直接开始循环的,但是由于有两个 0,next数组的值相比于秩,被耽误了 2 下,以至于循环里面每一个秩-next都等于循环节长度 2。

我原来想从前往后,发现 next 大于 0 的时候就用i - next[i]作为循环节长度,结果出了问题,因为循环节内部可能仍有重复的数字序列,比如一个循环节反过来是8123245,很快就会出现非零的 next 值。又想了其它方法,总有漏洞,其中一个甚至通过了所有数据的测试。

最后,我由于取得小数位数足够长,直接寻找next[i]的最大值位置了。这个位置,正是循环节消失的地方,i - next[i]是循环节长度,后面留下的小尾巴也就是非循环节的部分了。

结果快得吓人,尽管我小数部分取了那么多位,而且非常暴力地把小数部分前后倒置。线性时间比起稍微有点平方,果然是不知道高到哪里去了。

Compiling...
Compile: OK

Executing...
   Test 1: TEST OK [0.000 secs, 4248 KB]
   Test 2: TEST OK [0.000 secs, 5028 KB]
   Test 3: TEST OK [0.000 secs, 5028 KB]
   Test 4: TEST OK [0.000 secs, 4248 KB]
   Test 5: TEST OK [0.000 secs, 4256 KB]
   Test 6: TEST OK [0.000 secs, 5028 KB]
   Test 7: TEST OK [0.000 secs, 5032 KB]
   Test 8: TEST OK [0.000 secs, 5036 KB]
   Test 9: TEST OK [0.000 secs, 5028 KB]

All tests OK.
Your program ('fracdec') produced all correct answers!  This is your
submission #9 for this problem.  Congratulations!

拖到现在才完成这道题,深表惭愧。加油加油。

fracdec:没这么麻烦

看了题解之后觉得自己蠢哭了,明明只要判断目前的被除数之前有没有出现过,就可以知道后面有没有一样的片段了,我竟然非要除一大串然后去玩字符串,真是服了。数学!数学不好!

说来也是,后面的数字就是由目前的被除数决定的,没毛病。

代码:

#include <cstdio>
#define N 100100

int seen[N]; 
//这个被除数是不是已经被发现过,如果是,是在要除出来小数点后第几-1位时
int count = 0, len = 0; //count到76换行,len(decimal)
char decimal[N]; //记住算过的小数

int intLen(int num){ //统计整数的位数
    int l = 0;
    if(num == 0) return 1;
    while(num > 0){
        num /= 10;
        l ++;
    }
    return l;
}

int main(){
    FILE *fin = fopen("fracdec.in", "r");
    FILE *fout = fopen("fracdec.out", "w");
    
    int n, d, integer, start, end;
    fscanf(fin, "%d %d", &n, &d);
    integer = n / d; //分离整数部分
    count = intLen(integer);//计算整数部分位数
    
    n = n % d; //留下的产生小数部分
    seen[n] = 0 + 1;
    while(1){ //做除法,直至除尽或被除数重复
        decimal[len ++] = '0' + n * 10 / d;
        n = n * 10 % d;
        if(n == 0){
            start = -1; //没有循环节的
            break;
        }
        if(seen[n] > 0){ //见过这个被除数
            start = seen[n] - 1;
            end = len;
            break;
        }
        seen[n] = len + 1;
    }

    //输出
    fprintf(fout, "%d.", integer);
    count ++;
    if(start == -1){ //除尽
        for(int i = 0; i < len; i ++){
            if(count == 76) {fprintf(fout, "\n"); count = 0;}
            fprintf(fout, "%c", decimal[i]);
            count ++;            
        }
    }else{ //有循环节
        for(int i = 0; i < start; i ++){
            if(count == 76) {fprintf(fout, "\n"); count = 0;}
            fprintf(fout, "%c", decimal[i]);
            count ++;
        }
        fprintf(fout, "("); count ++;
        for(int i = start; i < end; i ++){
            if(count == 76) {fprintf(fout, "\n"); count = 0;}
            fprintf(fout, "%c", decimal[i]);
            count ++;
        }
        fprintf(fout, ")"); count ++;
    }
    fprintf(fout, "\n");

    return 0;
}

效果:

Compiling...
Compile: OK

Executing...
   Test 1: TEST OK [0.000 secs, 4664 KB]
   Test 2: TEST OK [0.000 secs, 4664 KB]
   Test 3: TEST OK [0.000 secs, 4664 KB]
   Test 4: TEST OK [0.000 secs, 4664 KB]
   Test 5: TEST OK [0.000 secs, 4664 KB]
   Test 6: TEST OK [0.000 secs, 4664 KB]
   Test 7: TEST OK [0.000 secs, 4664 KB]
   Test 8: TEST OK [0.000 secs, 4664 KB]
   Test 9: TEST OK [0.000 secs, 4664 KB]

All tests OK.
Your program ('fracdec') produced all correct answers!  This is your
submission #11 for this problem.  Congratulations!

还有更神奇的做法,还能推算到底是从第几位开始循环的,也就是算非循环节的小数位数。因为这些位数都是由于分子分母含有的 2 和 5 的因子不同导致的。抄题解代码:

int numBeforeRepeat(int n, int d) {
    int c2=0, c5=0;
    if (n == 0) return 1;
    while (d%2==0) { d/=2; c2++; }
    while (d%5==0) { d/=5; c5++; }
    while (n%2==0) { n/=2; c2--; } /* can go negative */
    while (n%5==0) { n/=5; c5--; } /* can go negative */
    if (c2>c5)
        if (c2>0) return c2;
        else return 0;
    else
        if (c5>0) return c5;
        else return 0;
}

是不是666。数学好的人真酷。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容