Google APAC 2016 University Graduates Test Round B解题报告

先传个笔试成绩的截图:
《Google APAC 2016 University Graduates Test Round B解题报告》

这次算是成绩最好的一次了,笔试结束后仍然可以练习,地址戳https://code.google.com/codejam/contest/10214486/dashboard

A. Travel

一看就是图论当中最短路的变形,但是边的权重会随着时间发生变化。对于dijkstra或者Bellman Ford之类的最短路算法,它们本质上都是动态规划,需要满足最优子结构性质,如果边权重随着时间的变化是不规则的,那么这些算法都无法保证得到最优解。

但是题目中有一个条件: cost(i)cost(i+1)+1 ,换句话说,不可能出现从一个顶点晚出发却早到终点的情况,于是不管是dijkstra, bellman ford, spfa都是仍然适用的,预先计算出1在所有时间点到所有点的最短路, O(1) 即可回答每一个查询。

#include <bits/stdc++.h>
#define FOR(i, n) for (int i = 0; i < (int)n; ++i)
using namespace std;
typedef long long ll;
typedef pair<int, int> point;
const int INF = INT_MAX / 2;
vector<int> cost[505][505];
void solve() {
    int N, M, K, x, y, c;
    scanf("%d %d %d", &N, &M, &K);
    FOR(i, N + 1) FOR(j, N + 1) cost[i][j].clear();
    FOR(i, M) {
        scanf("%d %d", &x, &y);
        FOR(j, 24) {
            scanf("%d", &c);
            cost[x][y].push_back(c);
            cost[y][x].push_back(c);
        }
    }
    int dis[505][24];
    FOR(i, N + 1) FOR(j, 24) dis[i][j] = INF;
    FOR(start, 24) {
        // spfa
        vector<bool> inq(N + 1, false);
        dis[1][start] = 0; inq[1] = true;
        queue<int> q;
        q.push(1);
        while (!q.empty()) {
            int tp = q.front(); q.pop(); inq[tp] = false;
            for (int i = 1; i <= N; ++i) {
                if (cost[tp][i].empty()) continue;
                int md = (start + dis[tp][start]) % 24;
                if (dis[i][start] > dis[tp][start] + cost[tp][i][md]) {
                    dis[i][start] = dis[tp][start] + cost[tp][i][md];
                    if (!inq[i]) { q.push(i); inq[i] = true; }
                }
            }
        }
    }
    int D, S;
    FOR(i, K) {
        scanf("%d %d", &D, &S);
        if (dis[D][S] == INF) {
            cout << " -1";
        }
        else cout << " " << dis[D][S];
    }
    cout << endl;
    return;
}
int main() {
    int TestCase;
    cin >> TestCase;
    FOR(caseID, TestCase) {
        cout << "Case #" << caseID + 1 << ":";
        solve();
    }
    return 0;
}

B.gWheels

我读了半天题才看明白什么意思。。。说白了就是有三个数组 a[1],a[2]...a[Np] b[1],b[2]...b[Ne] c[1],c[2]...c[Nt] ,然后要求从 a,c 数组中分别选择一个元素,从 b 数组中选择两个不同的元素,满足以下约束条件:

(ai×br)÷(bj×bl)=P÷Q

对于小数据暴力枚举就好了,对于大数据,可以预先计算出所有的 br/bl ,存到set里面,然后只枚举 a,c 数组,去set里面查找是否有满足要求的分数。下面的代码在大数据上跑了大概1分半种,可以满足8分钟以内的要求。

#include <bits/stdc++.h>
#define FOR(i, n) for (int i = 0; i < (int)n; ++i)
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pll;
void solve() {
    int np, ne, nt;
    scanf("%d %d %d", &np, &ne, &nt);
    vector<ll> gp(np), ge(ne), gt(nt);
    FOR(i, np) scanf("%lld", &gp[i]);
    FOR(i, ne) scanf("%lld", &ge[i]);
    FOR(i, nt) scanf("%lld", &gt[i]);
    set<pll> extra;
    FOR(i, ne) for (int j = i + 1; j < ne; ++j) {
        ll g = __gcd(ge[i], ge[j]);
        ll a = ge[i] / g, b = ge[j] / g;
        extra.insert(make_pair(a, b));
        extra.insert(make_pair(b, a));
    }
    set<pll> base;
    FOR(i, np) FOR(j, nt) {
        ll g = __gcd(gp[i], gt[j]);
        ll a = gp[i] / g, b = gt[j] / g;
        base.insert(make_pair(a, b));
    }
    int M;
    ll P, Q;
    scanf("%d", &M);
    set<pll>::iterator it;
    FOR(i, M) {
        bool ok = false;
        scanf("%lld %lld", &P, &Q);
        for (it = extra.begin(); it != extra.end(); ++it) {
            ll a = P * ((*it).first), b = Q * ((*it).second);
            ll g = __gcd(a, b);
            a /= g;
            b /= g;
            if (base.find(make_pair(a, b)) != base.end()) {
                ok = true;
                break;
            }
        }
        if (ok) printf("Yes\n");
        else printf("No\n");
    }
    return;
}
int main() {
    int TestCase;
    cin >> TestCase;
    FOR(caseID, TestCase) {
        cout << "Case #" << caseID + 1 << ":" << endl;
        solve();
    }
    return 0;
}

C. gNumbers

这是一个博弈论 + 数论的题目,稍微分析一下就能发现其实是个很常见的博弈题目,A(先手)必胜当且仅当有一种选择质因子的办法使得B(后手)必败,B必败当且仅当所有选择质因子的办法都使得A必胜。递归去求解就行了。

对于大数据, n1015 ,尽管如此,n的质因子个数也不会超过15个,因此递归的层数不多,完全是在时间接受范围内的,下面的代码在大数据上跑了不到2s。

#include <bits/stdc++.h>
#define FOR(i, n) for (int i = 0; i < (int)n; ++i)
using namespace std;
typedef long long ll;
typedef pair<int, int> point;
const char* FIRST = "Laurence";
const char* SECOND = "Seymour";
bool lose(ll num);
bool win(ll num);
vector<ll> factors;
inline bool isprime(int num) {
    assert(num >= 2);
    for (int i = 2; i * i <= num; ++i) {
        if (num % i == 0) return false;
    }
    return true;
}
inline bool gprime(ll num) {
    int cnt = 0;
    while (num > 0) {
        cnt += (num % 10);
        num /= 10;
    }
    return ((cnt == 1) || isprime(cnt));
}
bool lose(ll num) {
    if (gprime(num)) return true;
    FOR(i, factors.size()) {
        if (num % factors[i] == 0) {
            ll tmp = num;
            while (tmp % factors[i] == 0) tmp /= factors[i];
            if (!win(tmp)) return false;
        }
    }
    return true;
}
bool win(ll num) {
    if (gprime(num)) return false;
    FOR(i, factors.size()) {
        if (num % factors[i] == 0) {
            ll tmp = num;
            while (tmp % factors[i] == 0) tmp /= factors[i];
            if (lose(tmp)) return true;
        }
    }
    return false;
}
void solve() {
    ll n;
    cin >> n;
    factors.clear();
    ll tmp = n;
    for (ll i = 2; i * i <= tmp; ++i) {
        if (tmp % i == 0) {
            factors.push_back(i);
            while (tmp % i == 0) tmp /= i;
        }
    }
    if (tmp > 1) factors.push_back(tmp);
    if (win(n)) printf("%s\n", FIRST);
    else printf("%s\n", SECOND);
    return;
}
int main() {
    int TestCase;
    cin >> TestCase;
    FOR(caseID, TestCase) {
        cout << "Case #" << caseID + 1 << ": ";
        solve();
    }
    return 0;
}

D. Albocede DNA

我比赛的时候只搞出来了小数据,也是动态规划,枚举所有可能的valid sequence,但复杂度很高。

后来膜拜了kcm1700大神的代码,很简洁清晰。。。设置状态 dp[i][j][k] 表示当前已经在处理第 i 类基因( 0i3 ,分别表示a, b, c, d),还剩下j个a,k个b需要处理,根据题目约束, 0j,k<250 。这样状态转移方程如下:

dp[i][j][k]=dp[i][j+1][k],i=0,s[p]=a


dp[i][j][k]=dp[i][j][k+1],i=1,s[p]=b


dp[i][j][k]=dp[i][j1][k],i=2,s[p]=c


dp[i][j][k]=dp[i][j][k1],i=3,s[p]=d

还有其他一些状态之间转换的方程,类似推导一下就行,kcm1700大神的代码里写得很清楚,自己看就好了。。

// by kcm1700
#include <cstdio>
#include <climits>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <map>
#include <queue>
#include <cstring>
#include <string>
#include <set>
#include <deque>
#include <thread>

using namespace std;

const int mod = 1000000007;

long long dp[2][4][252][252];
char dat[512];

void add(long long &dest, long long val)
{
    dest = (dest+val) % mod;
}

int main()
{
    int T;
    scanf("%d",&T);
    for (int testcase = 1; testcase <= T; testcase++)
    {
        fprintf(stderr, "Case #%d processing\n", testcase);
        scanf("%s",dat);
        int n = strlen(dat);
        long long ans = 0;
        memset(dp,0,sizeof(dp));
        dp[0][0][0][0] = 1;
        for (int i = 0; i < n; i++) {
            int ci = i&1;
            int ni = !ci;
            memcpy(dp[ni], dp[ci], sizeof(dp[ci]));
            for (int state = 0; state < 4; state++) {
                for (int c1 = 0; c1 <= 250; c1++) {
                    for (int c2 = 0; c2 <= 250; c2++) {
                        auto cur =dp[ci][state][c1][c2];
                        if (cur==0)continue;
                        if (dat[i] == 'a') {
                            if (state == 0) {
                                add(dp[ni][state][c1+1][c2], cur);
                            }
                        } else if (dat[i] == 'b') {
                            if (state == 0) {
                                add(dp[ni][1][c1][1], cur);
                            } else if (state == 1) {
                                add(dp[ni][1][c1][c2+1], cur);
                            }
                        } else if (dat[i] == 'c') {
                            if (state == 1) {
                                if (c1 >= 2) {
                                    add(dp[ni][2][c1-1][c2],cur);
                                } else if (c1 == 1) {
                                    add(dp[ni][3][c1-1][c2],cur);
                                }
                            } else if (state == 2) {
                                if (c1 >= 2) {
                                    add(dp[ni][2][c1-1][c2],cur);
                                } else if (c1 == 1) {
                                    add(dp[ni][3][c1-1][c2],cur);
                                }
                            }
                        } else {
                            if (state == 3) {
                                if (c2 >= 2) {
                                    add(dp[ni][3][c1][c2-1],cur);
                                } else if (c2 == 1) {
                                    add(dp[ni][0][0][0],cur);
                                    add(ans, cur);
                                }
                            }
                        }
                    }
                }
            }
        }
        ans = (ans%mod+mod)%mod;
        printf("Case #%d: %lld\n",testcase, ans);
    }
    return 0;
}

总体来说,Round B比Round A要难了一点,不过也还算基本。。

点赞