首页 > 代码库 > Google Code Jam 2014 Round 2回顾和分析

Google Code Jam 2014 Round 2回顾和分析

回顾

比赛开始网络就一直在抽风,不知道宿舍网渣还是有人攻击服务器。刷了n遍n久刷出了题目。提交A小case的时候眼睁睁看着时间过去,却提交不上,这破网。最后A题B题还是解决了,C题扫了一眼,读都没读,就奔D题去了,因为我看到了熟悉的trie这个单词,加之看到小case只有9分,判断小case应该比较容易。前面因为网络浪费了很多时间,加之从未编过trie的程序,只能临时上网翻书去学,最后D小这个可以很容易暴力解的问题也没编完。

最终的rank是13xx,考虑到在这次GCJ之前从未接触过编程竞赛,而在这期间也几乎没练习,对这个结果还是比较满意的。尤其是并没有因为网络的问题心理受到多大影响,不过好像我本来就是这样的人。 比赛完看了下排名靠前的几个人,还是很震惊的。他们第一题做出来的时候我恐怕连题目都还没读完,四题全部做完的时候我可能刚刚做了第一题?更别论C和D题这种我想破脑袋也基本不会在规定时间做出来的对他们感觉也只是寻常。而智力方面绝不会有如此天壤之别,可见勤能补拙并非虚言,古之人诚不我欺!

顺带回顾下这次GCJ之前的round。

Qualification round 题目比较简单,也不限制时间,做了半天也做完了。本来也是因为临近毕业要找工作会问算法题,在小伙伴的怂恿下参与锻炼一下的,根本没打算继续参加。结果rank居然到了8xx,自信心一下爆棚,决定继续参与之后的round。

Round 1A 最后一道题都没做出来。总结了下,主要是因为没有比赛经验,以至于到这个时候完全没有比赛的状态,A题有思路最终也没做出来,B题直接就看错了题,C题据说很不寻常(题目里说的),不过对我这个新手来说真真是没这感觉。

Round1B 过了A题,C题想到算法,但始终没过。结束以后下了个答案跑了下,三个case不一样,用这几个case分析了下,结果是程序中一个小于和小于等于判断的区别。这让我认识到编程竞赛比较残酷的一面,要么全对,要么全错,but我喜欢(我真的没有受虐倾向→_→)。B题一看数据量,估计了下最多只能是O(n)的算法,可能又要有什么比特位之类的神奇操作,果断放弃了。一个教训,边界的处理要特别小心。

Round1C A题大case和B,C的小case都过了。B的大case算法也想到了,但牵扯的部分太多,没能在规定时间内编出来,虽然经历了前面几轮,这个时候依旧经验不足啊!C的大case也有一些思路,联想到了数学里面面积相同的多边形,圆的周长最短,这题就是个离散的版本,最后的形状应该接近于圆,有时间的话打算从一个个小数据开始找找规律。

分析

题目在http://code.google.com/codejam/contest/3014486/dashboard

A题

最小的文件肯定要放到一个CD中,为充分利用这个CD的剩余空间,放入能放进的最大的文件,去掉这两个文件后再重复进行同样的过程,直到所有文件都放进CD。看起来是个不错的贪心策略,下面说明它的正确性。
假设找到了一个最优解{f1, f2},{f3, f4},…;另外,用上面说的贪心策略得到的解{fa,fb}, {fc, fd},…。{}为一个CD中的内容, fx为这个CD文件的大小,有时CD中可能只有一个文件。现在要说明的是,贪心得到的CD数不会比最优解大。假设当前最小的文件是fa,fb是能放进装有fa的CD的最大的文件。现在从最优解中找到放fa的CD,比如{f1=fa, f2},有f2≤fb。

若f2=fb,那么去掉fa,fb之后对剩下的文件再进行同样的论证即可。
若f2若放进f1之后再放不进其它的文件,那么最优解中放f1的CD也没有另外的文件,去掉f1后对剩下的文件再进行同样的论证即可。

就这样将最优解向贪心获得的解转化,却不增加最优解的CD个数,可见这个贪心算法是正确的。

B题

依旧是贪心,依旧是从最小值开始考虑。o(╯□╰)o

选出数组中的最小值,这个值最后的归宿只有两种可能,要么交换到最左边,要么交换到最右边。想要交换次数最少,很自然的想法就是交换到较近的一侧。

比如数组[A1, A2,…,Am,…An-1,An],其中Am是最小值,由于我们只在意交换的次数,而并不在意交换的顺序,比如交换A1,A2,并不会影响Am的位置,这些与Am无关的交换就可以放到后面(等Am交换到目的位置之后)去做,现在只考虑Am的交换,Am最终要么去A1的位置,要么去An的位置。并且与Am交换并不会改变剩下元素的相对位置。

假设Am离A1比较近,按上面的贪心策略交换后,得到[Am,A1,A2,..,Am-1,Am+1,…,An-1,An],而若最优解最后把Am交换到了An,那么得到[A1,A2,..,Am-1,Am+1,…,An-1,An,Am],两种情况剩下要做的都是去解[A1,A2,..,Am-1,Am+1,…,An-1,An]这个除Am之后的子数组。而贪心策略将Am交换到目标位置的次数(m-1)比最优解(n-m)更少,矛盾。因此假设(最优解将Am交换到An)不成立,最优解只能把Am交换到A1。

C题

如前所述,扫了一眼,没读,就奔D去了。

D题

对小case,可以枚举所有的分配情况,每个串都可以选择去任意一个server,最多不超过44…*4=4^8=2^16,对每个分配的情况,建trie树,计算节点个数。如前所述,这个程序没有在规定时间内编完。

我的程序

头文件就不贴了。

A题

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
int N, X;
int pack[701];
int solve() {
    int tot=0;
    while (N > 0) {
        for (int ii = 1; ii <= X; ii++)
        {
            if (pack[ii] > 0) {
                pack[ii] -= 1;
                --N;
                for (int jj = X; jj >= 1; jj--)
                {
                    if (jj <= X-ii && pack[jj] > 0) {
                        --N;
                        --pack[jj];
                        break;
                    }
                }
                ++tot;
            }
        }
    }
    return tot;
}
int main() {
    int T;
    scanf("%d", &T);
    for (int i = 0; i < T; ++i) {
        scanf("%d %d", &N, &X);
        fill(pack, pack+X+1, 0);
        for (int j = 0; j < N; ++j) {
            int dx;
            scanf("%d", &dx);
            pack[dx] += 1;
        }
        int p = solve();
        printf("Case #%d: %d\n", i+1, p);
    }
    return 0;
}

B题

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
int N;
int arr[1001];
int solve() {
    int tot=0;
    int i = 0;
    int j = N-1;
    for (int xx = 0; xx < N; ++xx) {
        int min = INT_MAX;
        int mi = -1;
        for (int k = i; k <= j; ++k) {
            if (arr[k] < min) {
                min = arr[k];
                mi = k;
            }
        }
        if (mi - i < j - mi) {
            for (int l = mi; l >i; --l) {
                arr[l] = arr[l-1];
            }
            tot += mi-i;
            ++i;
        } else {
            for (int l = mi; l < j; ++l) {
                arr[l] = arr[l+1];
            }
            tot += j-mi;
            --j;
        }
    }
    return tot;
}
int main() {
    int T;
    scanf("%d", &T);
    for (int i = 0; i < T; ++i) {
        scanf("%d", &N);
        for (int j = 0; j < N; ++j) {
            scanf("%d", &arr[j]);
        }
        int p = solve();
        printf("Case #%d: %d\n", i+1, p);
    }
    return 0;
}

C题

没做。

D题

小case结束之后编出来的,就不放了吧。
大case没做。