2021 google kick start roundA kick start
的题目果真质量非常高,非常值得学习和练习的题目,非常喜欢这类的题目,前三题都可以做出来,不过题目D
确实很难,我感觉自己肯定想不到解答的答案的。前三题就是leetcode
的难度,最后一题确实很难。
K-Goodness String (5pts, 7pts) 题目 给定字符串,求出给定分值得最小变换次数
地址 https://codingcompetitions.withgoogle.com/kickstart/round/0000000000436140/000000000068cca3
题意
简单题目
思路
我们只需要求出$s[i] != s[n-1-i]$得字符对个数,最小的变换次数等于不同的字符对的个数与k
的绝对值之差。#include <iostream> #include <vector> #include <set> #include <unordered_set> #include <map> #include <unordered_map> #include <string> #include <stack> #include <algorithm> #include <limits.h> using namespace std;void slove (int t) { int good = 0 ; int bad = 0 ; int ans = 0 ; string str; int n,k; cin>>n>>k; cin>>str; for (int i = 0 ; i < (n>>1 ); ++i){ if (str[i] == str[n-1 -i]) good++; else bad++; } cout<<"Case #" <<t<<": " <<abs (k-bad)<<endl; } int main () { int t; cin>>t; for (int i = 0 ; i < t; ++i){ slove (i+1 ); } return 0 ; }
L Shaped Plots (8pts, 12pts) 题目 求出给定的矩阵中L
的个数,L
形要求:
L
的两个线段必须分别在一行和一列,两个线段必须必须与行、列平行;
L
的两个线段有且只有一个点相交,且一条线段的长度为另一条线段的长度的两倍;
L
的两个线段的长度必须大于等于2
;
地址 https://codingcompetitions.withgoogle.com/kickstart/round/0000000000436140/000000000068c509
题意
滑动窗口 + 前缀和
思路
我们分别求出以点(x,y)
为起点,分别往上下左右四个方向的最长的连续的1
的长度。
分别求出以点(x,y)
为起点相交顶点的L
的个数,分别在上下左右四个方向求一边,我们设L
的两个线段的长度分别为$m,n$,则以m,n
为长度的两个线段可能组成的L
的形状个数为: $$ cnt = max(0,min(m/2,n)-1) + max(0,min(m,n/2)-1) $$
时间复杂度$O(mn)$,空间复杂度$O(m n)$.
代码 #include <iostream> #include <vector> #include <set> #include <unordered_set> #include <map> #include <unordered_map> #include <string> #include <stack> #include <algorithm> #include <limits.h> using namespace std;void slove (int t) { int row; int col; int ans = 0 ; cin>>row>>col; vector<vector<int >> matrix (row,vector <int >(col)); vector<vector<int >> left (row,vector <int >(col)); vector<vector<int >> right (row,vector <int >(col)); vector<vector<int >> down (row,vector <int >(col)); vector<vector<int >> up (row,vector <int >(col)); for (int i = 0 ; i < row; ++i){ for (int j = 0 ; j < col; ++j){ cin>>matrix[i][j]; } } for (int i = 0 ; i < row; ++i){ int curr = 0 ; for (int j = 0 ; j < col; ++j){ if (matrix[i][j] == 1 ) curr++; else curr = 0 ; left[i][j] = curr; } curr = 0 ; for (int j = col-1 ; j >= 0 ; --j){ if (matrix[i][j] == 1 ) curr++; else curr = 0 ; right[i][j] = curr; } } for (int j = 0 ; j < col; ++j){ int curr = 0 ; for (int i = 0 ; i < row; ++i){ if (matrix[i][j] == 1 ) curr++; else curr = 0 ; up[i][j] = curr; } curr = 0 ; for (int i = row-1 ; i >= 0 ; --i){ if (matrix[i][j] == 1 ) curr++; else curr = 0 ; down[i][j] = curr; } } for (int i = 0 ; i < row; ++i){ for (int j = 0 ; j < col; ++j){ ans += max (min (left[i][j],up[i][j]/2 ) - 1 ,0 ); ans += max (min (left[i][j]/2 ,up[i][j]) - 1 ,0 ); ans += max (min (right[i][j],up[i][j]/2 ) - 1 ,0 ); ans += max (min (right[i][j]/2 ,up[i][j]) - 1 ,0 ); ans += max (min (left[i][j],down[i][j]/2 ) - 1 ,0 ); ans += max (min (left[i][j]/2 ,down[i][j]) - 1 ,0 ); ans += max (min (right[i][j],down[i][j]/2 ) - 1 ,0 ); ans += max (min (right[i][j]/2 ,down[i][j]) - 1 ,0 ); } } cout<<"Case #" <<t<<": " <<ans<<endl; } int main () { int t; cin>>t; for (int i = 0 ; i < t; ++i){ slove (i+1 ); } return 0 ; }
Rabbit House (9pts, 15pts) 这个题目跟leetcode
上的那个容器的题目很像,所以是中规中矩的题目,不算很难,靠自己还能思考出来。
地址 https://codingcompetitions.withgoogle.com/kickstart/round/0000000000436140/000000000068cb14
题意
优先级队列
思路
我们将矩阵中所有单元的高度按照从高到底依次排列,每次从队列中取除当前高度最高的单元$(x,y)$,高度为$h$,则此时必须满足$(x,y)$周围的四个格子$(x-1,y),(x+1,y),(x,y+1),(x,y-1)$必须要大于等于$h-1$,如果当前格子的高度发生改变则将其从新放入队列中。
非常简单的优先级对即可解决,时间复杂度为$O(mnlg(m n))$,空间复杂度为$O(m*n)$.
代码 #include <iostream> #include <vector> #include <set> #include <unordered_set> #include <map> #include <unordered_map> #include <string> #include <stack> #include <algorithm> #include <limits.h> #include <queue> using namespace std;struct Node { int x; int y; int val; Node (int x,int y,int val){ this ->x = x; this ->y = y; this ->val = val; } }; struct cmp { bool operator () (Node & a,Node & b) { return a.val < b.val; } }; void slove (int t) { int row; int col; long long ans = 0 ; int d[4 ][2 ] = {{0 ,1 },{0 ,-1 },{1 ,0 },{-1 ,0 }}; cin>>row>>col; vector<vector<int >> matrix (row,vector <int >(col)); vector<vector<bool >> visit (row,vector <bool >(col,false )); priority_queue<Node,vector<Node>,cmp> pq; for (int i = 0 ; i < row; ++i){ for (int j = 0 ; j < col; ++j){ cin>>matrix[i][j]; pq.push ({i,j,matrix[i][j]}); } } while (!pq.empty ()){ Node curr = pq.top (); pq.pop (); visit[curr.x][curr.y] = true ; if (curr.val != matrix[curr.x][curr.y]) continue ; for (int i = 0 ; i < 4 ; ++i){ int x = curr.x + d[i][0 ]; int y = curr.y + d[i][1 ]; if (x < 0 || y < 0 || x >= row || y >= col) continue ; if (visit[x][y]) continue ; if (matrix[x][y] < curr.val - 1 ){ ans += curr.val - 1 - matrix[x][y]; matrix[x][y] = curr.val - 1 ; pq.push ({x,y,matrix[x][y]}); } visit[x][y] = true ; } } cout<<"Case #" <<t<<": " <<ans<<endl; } int main () { int t; cin>>t; for (int i = 0 ; i < t; ++i){ slove (i+1 ); } return 0 ; }
Checksum (10pts, 17pts, 17pts) 题目 给定矩阵和每行每列的异或校验和,求恢复矩阵所需要的最小代价。
地址 https://codingcompetitions.withgoogle.com/kickstart/round/0000000000436140/000000000068c2c3
题意
最小生成树
解题思路
这个题目还是非常难的,根本不容易想到,我们首先将已经恢复的单元视作代价为$0$的未恢复的单元。
首先我们需要想到一个问题,思考一下我们至少需要恢复多少个单元,才能将所有的单元全部恢复,这个就是一个非常值得思考得问题,我们仔细想一下,因为已知每一行所有元素得异或校验和,则我们如果已经知道每一行的$n-1$个值,则我们就能通过校验和计算最后一个未知的数,对于每一列同样也是如此,所以我们可以猜测一下,我们至少需要恢复$(n-1)^{2}$个数据,才能完成所有的$n^{2}$数据恢复。
我们可以计算一下,如果恢复的元素超过$(n-1)^{2}$个,则我们可以计算出肯定存在某一行或者某一列恢复的数据为$n$个,这个对于我们来说是不必要的。
如果恢复的数据少于$(n-1)^{2}$,则此时我们还至少需要恢复$2*N$个数据,而我们实际上根据异或的校验和,至多能够推导出$2N-1$个数据。
根据以上推论,我们可以知道可以知道选择出最大的未解的$2n-1$个数据,剩余的$(n-1)^{2}$的数据恢复则花费的代价可能最小。如何选择$2n-1$个数据呢?假设我们按照以下顺序通过校验和来恢复最后剩余的$(2n-1)$个数据,顺序为:0 0 0 9 0 8 0 2 0 0 3 0 0 0 5 0 4 0 0 1 0 0 0 0 0 0 6 0 0 7 11 0 0 10 0 0
$1 \rightarrow 2 \rightarrow 3 \rightarrow 4\rightarrow 5\rightarrow 6\rightarrow 7\rightarrow 8\rightarrow 9\rightarrow 10 \rightarrow 11$ 我们实际可以知道将行与列分别看成生成树中的两个顶点,我们实际可以看出由行与列组成成的图不能有环,如果存在环的话,则一定存在某一行或者某一列中有两个未知数,则此时就不可能恢复该行或者该列的元素。
根据以上得出我们只需要选出边与行的顶点组成的最大生成树,可以用prim
或者Kruskal
算法即可。因为在此图中一个顶点连接的边很多,此时明显我们用prim
算法的时间复杂度更好。
代码直接copy lucifer
的。
代码 #include <algorithm> #include <cstdio> #include <iostream> #include <vector> using namespace std;using ll = long long ;template <typename T> void read (T &x) { x = 0 ; char c = getchar (); T sig = 1 ; for (; !isdigit (c); c = getchar ()) if (c == '-' ) sig = -1 ; for (; isdigit (c); c = getchar ()) x = (x << 3 ) + (x << 1 ) + c - '0' ; x *= sig; } class UnionFind { int n; vector<int > parent, size; public : UnionFind (int n) { this ->n = n; parent = vector <int >(n); size = vector <int >(n, 1 ); for (int i = 0 ; i < n; ++i) parent[i] = i; } int find (int idx) { if (parent[idx] == idx) return idx; return parent[idx] = find (parent[idx]); } void connect (int a, int b) { int fa = find (a), fb = find (b); if (fa != fb) { if (size[fa] > size[fb]) { parent[fb] = fa; size[fa] += size[fb]; } else { parent[fa] = fb; size[fb] += size[fa]; } } } }; class Solution {public : void solve (int case_num) { printf ("Case #%d: " , case_num); int n; read (n); vector<vector<int >> a (n, vector <int >(n)); vector<vector<int >> b (n, vector <int >(n)); vector<int > r (n) ; vector<int > c (n) ; for (int i = 0 ; i < n; ++i) for (int j = 0 ; j < n; ++j) read (a[i][j]); vector<tuple<int , int , int >> edges; ll tot = 0 ; for (int i = 0 ; i < n; ++i) for (int j = 0 ; j < n; ++j) { read (b[i][j]); tot += b[i][j]; edges.emplace_back (b[i][j], i, n + j); } sort (edges.rbegin (), edges.rend ()); UnionFind uf (n * 2 ) ; for (int i = 0 ; i < n; ++i) read (r[i]); for (int i = 0 ; i < n; ++i) read (c[i]); ll remove = 0 ; for (auto [weight, u, v] : edges) { if (uf.find (u) == uf.find (v)) continue ; remove += weight; uf.connect (u, v); } printf ("%lld\n" , tot - remove); } }; int main () { ios::sync_with_stdio (false ); cin.tie (0 ); int t; read (t); for (int i = 1 ; i <= t; ++i) { Solution solution = Solution (); solution.solve (i); } }
欢迎关注和打赏,感谢支持!