1. 程式人生 > >Google APAC 2016 University Graduates Test Round B解題報告

Google APAC 2016 University Graduates Test Round B解題報告

先傳個筆試成績的截圖:
rank

一看就是圖論當中最短路的變形,但是邊的權重會隨著時間發生變化。對於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; }

我讀了半天題才看明白什麼意思。。。說白了就是有三個陣列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;
}

這是一個博弈論 + 數論的題目,稍微分析一下就能發現其實是個很常見的博弈題目,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;
}

我比賽的時候只搞出來了小資料,也是動態規劃,列舉所有可能的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要難了一點,不過也還算基本。。