Google Kick Start 2020 Round H
Problem A - Retype
We only have two options, thus
Time complexity is .
Code (C++)
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
void solve(int case_num) {
cout << "Case #" << case_num << ": ";
long long N, K, S;
cin >> N >> K >> S;
cout << K - 1 + min(N + 1, K - S + N - S + 1) << endl;
}
};
int main() {
int t;
cin >> t;
for (int i = 1; i <= t; ++i) {
Solution solution = Solution();
solution.solve(i);
}
}
Problem B - Boring Numbers
All problems such that require counting of numbers within range can be transformed into solving for and separately, and taking their difference as the final answer.
Now suppose has digits and we want to count boring numbers within .
First, let's consider all numbers with digits. For digits, we can generate boring nubmers since we have options for each position (the most significant nubmer must be add so it cannot be ). So all numbers with digits make a contribution of .
Then we consider numbers with digits and are no larger than .
Start from the most significant digit, and suppose that we are at the -th digit now.
- If does not satisfy the requirement of parity, we just need to count the digits that are smaller than and can satisfy the parity (we can precalculate such numbers in ), then add to the total number . Since for these numbers, the following digits can be chose arbitrarily. In this case, we can stop right here.
- Otherwise, we first count the digits that are smaller than and can satisfy the parity (we can precalculate such numbers in ) and add to the total number . Then we are going to count boring numbers that have exactly same digits as and continue our processing. Note that if , we need to add to the total number, since this means itself is a boring number.
Time complexity is if we exclude the precalculations.
Code (C++)
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;
ll five[20], pre[20];
int a[10] = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4};
int b[10] = {0, 1, 1, 2, 2, 3, 3, 4, 4, 5};
class Solution {
ll count(ll x) {
string s = to_string(x);
int n = s.size();
ll ans = pre[n - 1];
for (int i = 1; i <= n; ++i) {
int c = s[i - 1] - '0';
if (c % 2 != i % 2) {
ans += five[n - i] * b[c];
break;
} else {
ans += five[n - i] * a[c];
if (i == n)
ans++;
}
}
return ans;
}
public:
void solve(int case_num) {
cout << "Case #" << case_num << ": ";
ll L, R;
cin >> L >> R;
cout << count(R) - count(L - 1) << endl;
}
};
int main() {
int t;
cin >> t;
five[0] = 1;
for (int i = 1; i < 20; ++i)
five[i] = five[i - 1] * 5;
pre[0] = 0;
for (int i = 1; i < 20; ++i)
pre[i] = pre[i - 1] + five[i];
for (int i = 1; i <= t; ++i) {
Solution solution = Solution();
solution.solve(i);
}
}
Problem C - Rugby
Apparently, we can solve for and independently.
First consider . Since all the people will be in the same row, this becomes a classical problem in which we just need to take the median of as the meeting place.
Then we consider . It is obvious that once we determine the starting point , the optimal movement is determined. The leftmost person will go to the leftmost cell, and the rest follow.
Thus we can solve this problem via ternary search. In order to prove the correctness, we need to prove that has only one extreme point, which is also its minimum point. (If we consider integer points, there might be two, but the two must be and ).
Obviously, when , decreases with . While when , increases with .
We then observe that, when we move the starting point from to , there will be people who will move less, and people who will move more. So . During the process where moves from to , goes to from , and will never increase. So will increase from to and will never increase. So will take its extreme value (also its minimum) at the minimum that makes .
The final time complexity is , in which is our search range.
Code (C++, ternary search)
#include <algorithm>
#include <climits>
#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;
class Solution {
public:
void solve(int case_num) {
cout << "Case #" << case_num << ": ";
int N;
cin >> N;
vector<int> X(N), Y(N);
for (int i = 0; i < N; ++i)
cin >> X[i] >> Y[i];
sort(Y.begin(), Y.end());
ll ylo = 0;
for (int yi : Y)
ylo += abs(yi - Y[N / 2]);
sort(X.begin(), X.end());
ll l = -2e9, r = 2e9;
ll xlo = LLONG_MAX;
auto dist = [&](ll start) {
ll ret = 0;
int idx = 0;
for (int xi : X) {
ret += abs(start + idx - xi);
idx++;
}
xlo = min(xlo, ret);
return ret;
};
while (l <= r) {
ll ml = l + (r - l) / 3, mr = r - (r - l) / 3;
ll dl = dist(ml), dr = dist(mr);
if (dl <= dr)
r = mr - 1;
if (dl >= dr)
l = ml + 1;
}
cout << ylo + xlo << endl;
}
};
int main() {
int t;
cin >> t;
for (int i = 1; i <= t; ++i) {
Solution solution = Solution();
solution.solve(i);
}
}
We can also do binary search on , or , and the solution is very similar.
Code (C++, binary search)
#include <algorithm>
#include <climits>
#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;
class Solution {
public:
void solve(int case_num) {
cout << "Case #" << case_num << ": ";
int N;
cin >> N;
vector<int> X(N), Y(N);
for (int i = 0; i < N; ++i)
cin >> X[i] >> Y[i];
sort(Y.begin(), Y.end());
ll ylo = 0;
for (int yi : Y)
ylo += abs(yi - Y[N / 2]);
sort(X.begin(), X.end());
ll l = -2e9, r = 2e9;
ll xlo = LLONG_MAX;
auto dist = [&](ll start) {
ll ret = 0;
int idx = 0;
for (int xi : X) {
ret += abs(start + idx - xi);
idx++;
}
xlo = min(xlo, ret);
return ret;
};
while (l <= r) {
ll mid = (l + r) / 2;
ll delta = dist(mid + 1) - dist(mid);
if (delta >= 0)
r = mid - 1;
else
l = mid + 1;
}
cout << ylo + xlo << endl;
}
};
int main() {
int t;
cin >> t;
for (int i = 1; i <= t; ++i) {
Solution solution = Solution();
solution.solve(i);
}
}
Actually, we can also apply the median method on . But we need to substitute with after the first sort, and then do a second sort. Detailed explanation can be seen in the official analysis.
#include <algorithm>
#include <climits>
#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;
class Solution {
public:
void solve(int case_num) {
cout << "Case #" << case_num << ": ";
int N;
cin >> N;
vector<int> X(N), Y(N);
for (int i = 0; i < N; ++i)
cin >> X[i] >> Y[i];
sort(Y.begin(), Y.end());
ll ylo = 0;
for (int yi : Y)
ylo += abs(yi - Y[N / 2]);
sort(X.begin(), X.end());
for (int i = 0; i < N; ++i)
X[i] -= i;
sort(X.begin(), X.end());
ll xlo = 0;
for (int xi : X)
xlo += abs(xi - X[N / 2]);
cout << ylo + xlo << endl;
}
};
int main() {
int t;
cin >> t;
for (int i = 1; i <= t; ++i) {
Solution solution = Solution();
solution.solve(i);
}
}
Problem D - Friends
If we build a graph with the strings, we will have too many edges.
So instead we can build a graph with different letters (in this problem we have letters). We will save this graph in an adjacent matrix.
The initial setting is and . Then we enumerate on all strings. If two different letters and both occur in the same string , we set . The meaning is that, if we have a string with and another string with , we can build a chain which has middle point.
Then we do Floyd-Warshall on this adjacent matrix. Now means the minimum middle points that are needed to build a chain from to .
For each query, we enumerate on letters in and , and the final answer will be
If the answer is , we just output .
The total time complexity is , in which is the size of the alphabet.
Code (C++)
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
const int INF = 0x3f3f3f3f;
class Solution {
public:
void solve(int case_num) {
cout << "Case #" << case_num << ": ";
int N, Q;
cin >> N >> Q;
vector<string> S(N + 1);
for (int i = 1; i <= N; ++i)
cin >> S[i];
vector<vector<int>> d(26, vector<int>(26, INF));
for (string s : S)
for (char c1 : s)
for (char c2 : s)
if (c1 != c2)
d[c1 - 'A'][c2 - 'A'] = 1;
for (int k = 0; k < 26; ++k)
for (int i = 0; i < 26; ++i) {
if (i == k)
continue;
for (int j = 0; j < 26; ++j) {
if (j == i || j == k)
continue;
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
}
for (int i = 1; i <= Q; ++i) {
int X, Y;
cin >> X >> Y;
int ans = INF;
bool found = false;
for (char c1 : S[X]) {
for (char c2 : S[Y]) {
if (c1 == c2) {
cout << 2 << " ";
found = true;
break;
}
ans = min(ans, d[c1 - 'A'][c2 - 'A'] + 2);
}
if (found)
break;
}
if (!found)
cout << (ans == INF ? -1 : ans) << " ";
}
cout << endl;
}
};
int main() {
int t;
cin >> t;
for (int i = 1; i <= t; ++i) {
Solution solution = Solution();
solution.solve(i);
}
}