kickstart 2021 roundC
最喜欢的kcick start
来了,但是由于本次round
的时候家里有事,正在陪小孩一直也没有时间来参加,所以只能等到比赛结束后来补上了,非常喜欢google kickstart
的题目,质量很高又非常有趣。
a.Smaller Strings (6pts, 9pts)
题目
地址
https://codingcompetitions.withgoogle.com/kickstart/round/0000000000435c44/00000000007ebe5e
题意
数位
dp
思路
- 题目中要求给定的字符串
s
,长度为n
,给定最大字符k,求所出长度为n
的且字典序严格小于s
的回文字符串的个数,且构成的回文子符串中所有的字符均小于等于k
。 - 典型的数位
dp
.由于是回文字符串,因此我们只需要找到字符串的前半部分即可,因为后半部分与前半部分相同,我们设dp[i]
表示字符串长度为i
且符合最大字符小于等于k
,且字典序严格小于字符串s
的前i
个字符构成的字符串的个数。limit
表示当前字符串s
中的前i
个字符是否存在字符大于等于k
,limit = 1
表示不存在不符合要求的字符,limit = 0
表示存在大于k
的字符。 - 我们可以知道递推公式:
当我们选择第i
个字符时:
- 如果当前字符串的前
i-1
个字符构成的字符串的字典序严格小于s
的前i-1
个字符构成的字典序,则在第i
个字符我们可以在[a,a+k]
中我们可以任意取值; - 如果当前字符串的前
i-1
个字符构成的字符串刚好等于s
的前i-1
个字符构成的字符串,则第i
位我们就只能取严格小于s[i]
且小于等于“a"+k
的字符;
- 最后需要检测一下,如果把整个数组的前半部分全部转换为回文字符串,则此时需要去检查是否字典序严格小于字符串$s$,算法时间复杂度为$O(n)$.
代码
|
b. Alien Generato
题目
地址
https://codingcompetitions.withgoogle.com/kickstart/round/0000000000435c44/00000000007ec1cb
题意
数学计算
思路
- 这个题目非常简单,感觉就是送分题,也就
leetcode
中等难度的题目。 - 我们得出计算公式为:
$$
g = n*k + \frac{(n-1)*n}{2} \
k = \frac{g-\frac{(n-1)*n}{2}}{n}
$$
此时我们只需要判断$g-\frac{(n-1)*n}{2}$是否能够整除$n$即可,且商大于等于1
. - 我们很轻易的可以判断$n \le \sqrt{g}$,我们只需要暴力测试所有可能的
n
即可。算法时间复杂度为$O(lgn)$.
代码
|
c.Rock Paper Scissors
地址
https://codingcompetitions.withgoogle.com/kickstart/round/0000000000435c44/00000000007ec28e
题意
动态规划
思路
- 这个题目非常有意思的
dp
,题目很长,大意就是说两个人玩石头剪刀布的游戏,每个人每轮可以在(r,p,s)
分别代表石头、剪刀、布中任意选择一个,石头可以赢剪刀,剪刀可以赢布,布可以赢石头。对方在第i
轮出手时,会根据当前已方已经出手的统计次数,来计算出自己选择的概率。假设第i
轮时,已方已经出示过的石头、布、剪刀的统计次数分别为$(r_{i},p_{i},s_{i})$,且已知$r_{i} + p_{i} + s_{i} = i$,则在第i+1
轮时,对方选择出示石头的概率为$\frac{s_{i}}{i}$,选择布的概率为$\frac{r_{i}}{i}$,选择剪刀的概率为$\frac{p_{i}}{i}$.需要注意的是,在第1
轮时,因为没有前面轮次的统计数据,则对方选择出示石头剪刀布的概率均为$\frac{1}{3}$,每天一共需要进行游戏60
轮,每天给定每一局赢时获得的分数W
,以及平局是获得的分数E
,给定一个特定的分数X
,求出游戏进行T
天后,每天的期望的平均值大于等于X
的每天的游戏的出牌方案。 - 这个题目很长,初看起来还是非常的复杂,不过仔细思考一下,其实也不算是特别难的。首先需要思考的几点:
- 题目要求给定满足期望平均值大于等于
X
的方案,那么假设我们每天都以期望最大值即最优的方案来出牌,肯定可以满足题意。所以我们就不用再去讨论其他的可能的方案,按照这个思路转化,本题即转换为求出每天期望值最大的出牌方案。 - 如果求出期望最大值的方案?实际上应该有多少种组合,我们如果每一轮都按照该轮期望最大值的方案进行出牌是不是一定能够得到最终的期望的最大值?这几个问题是值得深入思考的。
- 试想如果我们把所有的出牌的顺序的期望值都求一遍,肯定可以求得期望最大值的方案,则此时的算法时间复杂度为$3^{60}$,肯定会超时,有没有可能更简单的枚举方法可以来求?则此时我们应该想到用动态规划的子状态,我们设
dp[r][p][s]
表示当前已方已经出示了$r$次石头,$p$次布,$s$次剪刀时的最大期望值,则我们可以发现此时游戏一共进行了$ t = r + p + s$轮,则我们发现最优状态与第t
轮的选择有直接关系。 - 我们发现最优状态之间存在关系如下:
- 假设第
t
轮己方选择的是石头,则第t
轮对方选择为剪刀时己方会赢,而此时对方选择为剪刀的概率为$\frac{p}{r+p+s-1}$;对方选择的是石头时,己方会平局,则此时对方选择为石头的概率为$\frac{s}{r+p+s-1}$.因此我们可以推导如下:
$$
dp[r][p][s] = max(dp[r][p][s],dp[r-1][p][s] + W\frac{p}{r+p+s-1} + E\frac{s}{r+p+s-1})
$$
- 假设第
- 假设第
t
轮己方选择的是布,则第t
轮对方选择为石头时己方会赢,而此时对方选择为石头的概率为$\frac{s}{r+p+s-1}$;对方选择的是布时,己方会平局,则此时对方选择为布的概率为$\frac{r}{r+p+s-1}$.因此我们可以推导如下:
$$
dp[r][p][s] = max(dp[r][p][s],dp[r][p-1][s] + W\frac{s}{r+p+s-1} + E\frac{r}{r+p+s-1})
$$
- 假设第
- 假设第
t
轮己方选择的是剪刀,则第t
轮对方选择为布时己方会赢,而此时对方选择为布的概率为$\frac{r}{r+p+s-1}$;对方选择的是剪刀时,己方会平局,则此时对方选择为剪刀的概率为$\frac{p}{r+p+s-1}$.因此我们可以推导如下:
$$
dp[r][p][s] = max(dp[r][p][s],dp[r][p][s-1] + W\frac{r}{r+p+s-1} + E\frac{p}{r+p+s-1})
$$
- 假设第
- 我们依次遍历所有的可能即可得到在满足$r + p + s = 60$时的最大期望值,并记录下最大期望值时,$(r,p,s)$分别是多少。
- 最后还有关键的一步,我们如何找到最优的步骤,此时我们可以设定一个数字$choose[r][p][s]$表示当前选择为$(r,p,s)$时,则此时的最后一轮出示的到底是石头、剪刀还是布。我们分别对相应的计数进行减
1
,寻找前一个最优解的最后一轮出示的内容,则依次我们即可找到最优解,最后进行倒序即可。感觉google
给的分析解答貌似表达式有问题,还是自己琢磨最靠谱。
代码
|
d.Binary Operator
题目
地址
https://codingcompetitions.withgoogle.com/kickstart/round/0000000000435c44/00000000007ec290
题意
hash
解题思路
- 最后一题真心挺难,看了很长时间没有看懂题意,Q4全部做出来且测试用例全部通过的全球不到
100
人。题目大概的意思是可以对题目中给定的表达式进行化简,然后将化简得表达式进行分类,将表达式相同的进行划分为同一类,最终输出每个表达式的分类。 - 仔细看一下感觉题目还是非常的复杂,但是最终涉及到需要对表达式进行化简处理,还需要对表达式进行移位和合并计算,刚开始意味非常的麻烦,最后看了大家的解法,各种神奇的解法。
Errichto
的解法是用一个非常特殊的hash
运算来替代#
运算,lucifer1004
的解法是把所有可能的运算都计算一遍,如果发现所有可能运算结果都相等,则认为两个表达式相等,否则则认为不相等。确实是暴力出奇迹的解法,如果按照这两种思路来求解表达式的值的话,这题反而没有那么难了,仔细检查了一下答案发现好多人都用这种解法来完成,当然这种解法存在一定的概率导致出现冲突而计算错误,但是实际商概率非常小。
- 主要是题目中涉及到这个
#
的处理,如果完全用表达式的话,则确实不太好处理,反而我们换种思路去测试它的计算结果是否一致来检测两个表达式是否相等。求该表达式求值本身比较简单,利用后缀式即可,利用栈来处理,这个是常规做法。 - 官方给的解答是将所有的表达式转化为逆波兰式,然后再通过某种调整的方法对二叉树进行调整,最终判定逆波兰式的二叉树是否相等,不过确实好复杂。
代码
- 暴力测试所有可能的运算
from sys import stdin
from random import randint
randoms = [randint(-1000, 1000) for _ in range(100)]
def read_int():
return int(stdin.readline())
def read_ints():
return map(int, stdin.readline().split(' '))
def plus(x, y):
return x + y + randoms[0]
def sub(x, y):
return x - y + randoms[1]
def mul(x, y):
return x * y + randoms[2]
def my_div(x, y):
return x // (abs(y) + 1) + randoms[3]
def my_inv_div(x, y):
return y // (abs(x) + 1) + randoms[4]
def my_mod(x, y):
return x % (abs(y) + 1) + randoms[5]
def my_inv_mod(x, y):
return y % (abs(x) + 1) + randoms[6]
def bit_and(x, y):
return x & y + randoms[7]
def bit_or(x, y):
return x | y + randoms[8]
def bit_xor(x, y):
return x ^ y + randoms[9]
def const(x, y):
return randoms[10]
def only_x(x, y):
return x + randoms[11]
def only_y(x, y):
return y + randoms[12]
def quad_x(x, y):
return x * x + y + randoms[13]
def quad_y(x, y):
return x + y * y + randoms[14]
funcs = [plus, sub, mul, my_div, my_mod, my_inv_div, my_inv_mod,
bit_and, bit_or, bit_xor, const, only_x, only_y, quad_x, quad_y]
class Expression:
def __init__(self, s):
self.s = '(' + s + ')'
def evaluate(self, func):
nums = []
ops = []
curr = 0
is_num = False
for c in self.s:
if c == '(':
if is_num:
nums.append(curr)
is_num = False
ops.append('(')
elif c in '+*#':
if is_num:
nums.append(curr)
is_num = False
ops.append(c)
elif c == ')':
if is_num:
nums.append(curr)
is_num = False
while ops[-1] != '(':
op = ops.pop()
b = nums.pop()
a = nums.pop()
if op == '+':
nums.append(a + b)
elif op == '*':
nums.append(a * b)
else:
nums.append(func(a, b))
ops.pop()
else:
if not is_num:
curr = 0
is_num = True
curr = curr * 10 + int(c)
assert(len(nums) == 1)
return nums[0]
def __eq__(self, other):
for func in funcs:
a = self.evaluate(func)
b = other.evaluate(func)
if a != b:
return False
return True
t = read_int()
for case_num in range(1, t + 1):
n = read_int()
expressions = [Expression(input()) for _ in range(n)]
color = [0] * n
color[0] = 1
new_color = 2
for i in range(1, n):
for j in range(i):
if expressions[i] == expressions[j]:
color[i] = color[j]
break
if color[i] == 0:
color[i] = new_color
new_color += 1
print(f"Case #{case_num}: {' '.join(map(str, color))}") - 利用特殊的
hash
替代#
using namespace std;
sim > struct rge { c b, e; };
sim > rge<c> range(c i, c j) { return rge<c>{i, j}; }
sim > auto dud(c* x) -> decltype(cerr << *x, 0);
sim > char dud(...);
struct debug {
~debug() { cerr << endl; }
eni(!=) cerr << boolalpha << i; ris; }
eni(==) ris << range(begin(i), end(i)); }
sim, class b dor(pair < b, c > d) {
ris << "(" << d.first << ", " << d.second << ")";
}
sim dor(rge<c> d) {
*this << "[";
for (auto it = d.b; it != d.e; ++it)
*this << ", " + 2 * (it == d.b) << *it;
ris << "]";
}
sim dor(const c&) { ris; }
};
// debug & operator << (debug & dd, P p) { dd << "(" << p.x << ", " << p.y << ")"; return dd; }
const int mod = 1000050131;
char s[10005];
int hasz(int a) {
return ((100594677LL * (a + 450617412)) ^ 208774486) % mod;
}
int merge(int a, int b, char ope) {
if(ope == '+') {
return (a + b) % mod;
}
if(ope == '*') {
return (long long) a * b % mod;
}
assert(ope == '#');
return (((337807718LL * hasz(a)) + 254452523) ^ ((842188890LL * hasz(b)) + 900099649)) % mod;
}
int get_balance(char c) {
if(c == '(') {
return 1;
}
if(c == ')') {
return -1;
}
return 0;
}
int rec(int L, int R) {
// debug() << imie(L) imie(R);
int bal = 0;
for(int i = L; i <= R; ++i) {
bal += get_balance(s[i]);
}
assert(bal == 0);
if(s[L] == '(') {
int balance = 1;
int i = L + 1;
while(balance > 0) {
balance += get_balance(s[i]);
i++;
}
// past closing bracket
int a = rec(L + 1, i - 2); // this was inside the brackets
if(i >= R) {
return a;
}
assert(s[i] == '#' || s[i] == '+' || s[i] == '*');
// debug() << imie(i+1) imie(R);
int b = rec(i + 1, R);
return merge(a, b, s[i]);
}
int a = 0;
while(L <= R && isdigit(s[L])) {
a = (10LL * a + (s[L] - '0')) % mod;
L++;
}
if(L > R) {
return a;
}
// debug() << imie(L);
assert(s[L] == '+' || s[L] == '*' || s[L] == '#');
int b = rec(L + 1, R);
return merge(a, b, s[L]);
}
void test_case() {
int n;
scanf("%d", &n);
map<int, int> mapping;
for(int i = 0; i < n; ++i) {
scanf("%s", s);
int value = rec(0, (int) strlen(s) - 1);
if(mapping.find(value) == mapping.end()) {
int nxt = mapping.size();
nxt++;
mapping[value] = nxt;
}
printf(" %d", mapping[value]);
// debug() << imie(value);
}
puts("");
}
int main() {
int T;
scanf("%d", &T);
for(int z = 1; z <= T; z++) {
printf("Case #%d:", z);
test_case();
}
}
欢迎关注和打赏,感谢支持!
- 关注我的博客: http://mikemeng.org/
- 关注我的知乎:https://www.zhihu.com/people/da-hua-niu
- 关注我的微信公众号: 公务程序猿
扫描二维码,分享此文章