GCJ Qualification Round 2017

来源:互联网 发布:数据精灵免费版 编辑:程序博客网 时间:2024/05/18 10:20

、#GCJ Qualification Round 2017

第一次参加GCJ资格赛。苟且过了第一关的资格赛。 资格赛一共4题, 给了27个小时的时间。满分100分,高于25分者就有资格参加一周后的 GCJ Round 1。 每题都有small 和 large 两组测试数据。small 可以提交多次, 而 large 只要点击下载了数据,8分钟内必须提交结果和代码,只有一次提交机会。

题目地址:https://code.google.com/codejam/contest/3264486/dashboard
官方题解:https://code.google.com/codejam/contest/3264486/dashboard#s=a

Problem A. Oversized Pancake Flipper

题目大意:

有一排煎饼,正面有笑脸,反面没有。你有一个超大的翻转煎饼的铲子, 一次能且只能翻动K个煎饼, 不能多不能少, 翻动完的煎饼前后顺序与翻动之前不变。给定煎饼的初始状态,’ - ’ 表示 反面, ’ + ’ 表示正面。问至少需要翻动多少下,才能让所有煎饼都正面朝上。如果无法做到,就输出IMPOSSIBLE。

输入格式:

第一行一个数字T,表示测试数据组数。
接下来每行一个字符串,由’ - ’ 和 ’ + ’ 组成,一个数字K。

输出格式:

Case #x: y
x为第几组数据, y为最少要翻动的次数,或者IMPOSSIBLE

样例:

Input

3
—+-++- 3
+++++ 4
-+-+- 4

Output :

Case #1: 3
Case #2: 0
Case #3: IMPOSSIBLE

题解:

对于small 测试数据,用bfs枚举所有可能的翻动情况, 也是可以解决的。但对于 large 测试数据, bfs 8分钟也是跑不完的。。别问我怎么知道的。。
仔细观察,连续K个煎饼的区间,这个区间如果被翻动2次,就等于没有被翻动。所有被翻动的区间的顺序是无所谓的。所以每个不同的区间,最多只可能被翻动1次。我们观察到最左边的煎饼p1只受最左边的翻动f1的影响(f1就是翻动第1~第K个煎饼)。如果第一个煎饼是反面,想改变p1的状态,那就只能执行最左边的翻动f1。接下来同理,f2也只受p2的影响。这样我们就找到了一个贪心的策略。复杂度为O(n^2)

代码:

#include <iostream>using namespace std;int T, K, ans;string str;int main() {    std::ios::sync_with_stdio(false);    cin >> T;    for (int t = 1; t <= T; t++) {        cin >> str >> K;        ans = 0;        int len = str.length();        for (int i = 0; i < len-K+1; i++) {            if (str[i] == '-') {                ans++;                for (int j = i; j < i+K; j++) {                    if (str[j] == '-') str[j] = '+';                    else str[j] = '-';                }            }        }        bool flag = true;        for (int i = len-K+1; i < len; i++)            if (str[i] == '-') {                flag = false;                break;            }        cout << "Case #" << t << ": ";        if (flag) cout << ans << endl;        else cout << "IMPOSSIBLE" << endl;    }    return 0;}





Problem B. Tidy Numbers

题目大意:

给定一个正整数N,求最大的小于N的tidy数。tidy数的定义是,从高位到地位的数字为不下降序列。比如122233是tidy数, 20, 321 9990不是tidy数

输入:

第一行 T, 表示测试数据组数
接下来每行一个正整数N

输出:

Case #x: y
其中x表示第x组测试数据,y为满足条件的最大的tidy数

题解:

又一个贪心题。
从高位往低位,找到第一个N[i] > N[i+1]的点,在从i往前找与N[i]相同的数字,找到最左边的N[k] = N[i],然后N[k]到N[i]统统减1,N[i+1]到最后,统统变成9。最后输出的时候再去除前导0,就完成了。\( • ̀ω•́ )/。

代码:

#include <iostream>#include <string>using namespace std;int T;string str;int main() {    cin >> T;    for (int t = 0; t < T; t++) {        cin >> str;        int len = str.length();        for (int i = 0; i < len-1; i++) {            if (str[i] > str[i+1]) {                int k;                for (int j = i; j >= 0; j--) {                   if (str[i] == str[j])                       k = j;                }                str[k]--;                for(int j = k+1; j < len; j++)                    str[j] = '9';                break;            }        }        int k = 0;        for (int i = 0; i < len; i++)            if (str[i] != '0') {                k = i;                break;            }         cout << "Case #" << t+1 << ": ";        for (int i = k; i < len; i++)            cout << str[i];        cout << endl;    }    return 0;} 





Problem C. Bathroom Stalls

题目大意:

澡堂有N+2个连续的位置,开头和结尾始终有人,所以还有N个空位置。现在有K个人要陆续的来。新来的人总是会找一个距离两边的人距离最远的位置。假定之前来了的人不会中途离开。
具体的讲,设Ls,RS 分别为一个空位置与左边的人的距离 和 这个空位置与右边的人的距离。这个距离指的是间隔的空位置的个数。
1.找一个位置使得 min(LS,RS) 最大
2.如果新来的人发现只有一个这样的位置,那就选择这个位置。
3.否则,选择满足条件1的,使得max(LS,RS) 最大的那个。
4. 如果还是有多个选择, 那就选择最左边的。

求最后一个人进来时,它选择的位置的 max(LS,RS)min(LS,RS)

输入:

第一行 T, 表示测试数据组数
接下来T行,每行两个数字 N, K

输出:

Case #x y z
其中x为测试数据编号, y, z 分别为max(LS,RS),min(LS,RS)

1<=N<=1018

题解:

min(LS,RS)+max(LS,RS)+1=N, 所以min(LS,RS)max(LS,RS) 可以同时求出来。
先看特殊情况, 当N=K时, min(LS,RS)=max(LS,RS)=0
当K=1时, max(LS,RS)=N/2,min(LS,RS)=NN/21
开始找规律

我们定义一个函数LR(n,k), 返回一个pair, <max(LS,RS),min(LS,RS)>
k = 0 或者 k = n时,min(LS,RS)=max(LS,RS)=0
k = 1时, 就是把N从中间切分成两半, 两半的大小可以相等或者相差1。给他们编号为(1), (2)
k = 2时, 就是把刚刚分成的2半中较大的那一个再切分成两半。假设刚刚(2)比较大,则把(2)分成两半。现在的编号分别为(1), ((3), (4))
k = 3时,这次是把(1)分成2半,现在的编号为((5), (6)), ((3), (4))

我们发现,自从第一次把N切分成左右两半后,无论k为何值, 左右两边被切分的次数相差不会超过1。而左右两半的大小相差也不超过1。更大的那一半,切分的次数应该要比较小的那一半要多, 所以我们得到如下递推式:
LR(n,k)=max(LR(n/2,kk/2),LR(nn/21,k/2))
LR(n/2,kk/2) 就是较大的一半, LR(nn/21,k/2) 就是较小的一半。

由于N的范围太大,无法开数组来进行动态规划,比赛时跑了8分钟也没跑完。第一时间想到的优化策略是,如果切分成的两半的n和k完全相等,则只需要计算一次。然而即使这样还是存在大量的重复计算。
比赛结束后, 想到可以用一个map来存储计算过的状态,记忆化搜索,这样复杂度就降到了O(logn)。只加了几行代码,large测试数据就直接秒过。

代码:

#include <iostream>#include <cstdio>#include <map>using namespace std;typedef unsigned long long llu;typedef pair<llu, llu> P;map<P, P> Map;int T;llu N, K;P ans;P LR(llu n, llu k) {    //记忆化搜索    if (Map.find(make_pair(n, k)) != Map.end())         return Map[make_pair(n, k)];    if (n < k || k <= 0 || n <= 0) return make_pair(0, 0);    if (k == 1) {        llu mlr = (n+1)/2 - 1;        llu Mlr = n - 1 - mlr;        return make_pair(Mlr, mlr);    }    else if (n == k) return make_pair(0, 0);    else {        P tmp;        llu tn = n/2, tk = k/2;        if (tn == n-n/2-1 && tk == k-k/2) tmp = LR(tn, tk);        else tmp = max(LR(n/2, k-k/2), LR(n-n/2-1, k/2));        return Map[make_pair(n, k)] = tmp;//记录状态    }}int main() {    scanf("%d", &T);    for (int i = 1; i <= T; i++) {        scanf("%llu%llu", &N, &K);        ans = LR(N, K);        printf("Case #%d: ", i);        printf("%llu %llu\n", ans.first, ans.second);    }    return 0;}





Problem D. Fashion Show

0 0
原创粉丝点击