康复训练_普及组
1998年
三连击
普通模拟
阶乘和
高精度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 struct bigint { int num[N], len; }a, b, c; inline void Read (int x, bigint &t) { string s = to_string (x); t.len = s.length (); for (int i = 0 ; i < t.len; ++ i) t.num[i] = s[i] - '0' ; reverse (t.num, t.num + t.len); return ; } inline void Plus (bigint &a, bigint &b, big int &c) { c.len = max (a.len, b.len); for (int i = 0 ; i < c.len; ++ i) c.num[i] = a.num[i] + b.num[i]; for (int i = 0 ; i < c.len; ++ i) if (c.num[i] > 9 ) c.num[i + 1 ] += 1 , c.num[i] -= 10 ; if (c.num[c.len + 1 ]) ++ c.len; return ; } inline void Minus (bigint &a, bigint &b, big int &c) { c.len = a.len; for (int i = 0 ; i < c.len; ++ i) c.num[i] = a.num[i] - b.num[i]; for (int i = 0 ; i < c.len; ++ i) if (c.num[i] < 0 ) c.num[i + 1 ] -= 1 , c.num[i] += 10 ; while (c.len > 1 && c.num[c.len - 1 ] == 0 ) -- c.len; return ; } inline void Multi (bigint &a, int b, bigint &c) { c.len = a.len; for (int i = 0 ; i < c.len; ++ i) c.num[i] = a.num[i] * b; for (int i = 0 ; i < c.len; ++ i) { c.num[i + 1 ] += c.num[i] / 10 ; c.num[i] %= 10 ; if (c.num[i + 1 ] && i + 1 == c.len) ++ c.len; } return ; } inline void Multi (bigint &a, bigint &b, bigint &c) { c.len = a.len + b.len; for (int i = 0 ; i < a.len; ++ i) for (int j = 0 ; j < b.len; ++ j) c.num[i + j] += a.num[i] * b.num[j]; for (int i = 0 ; i < c.len; ++ i) if (c.num[i] > 9 ) c.num[i + 1 ] += (c.num[i] / 10 ), c.num[i] %= 10 ; while (c.num[c.len - 1 ] == 0 && c.len > 1 ) -- c.len; return ; } inline int Divide (bigint &a, int b, bigint &c) { c.len = a.len; ll t = 0 ; for (int i = c.len - 1 ; i >= 0 ; -- i) { t = t * 10 + a.num[i]; c.num[i] = t / b; t %= b; } while (c.len > 1 && c.num[c.len - 1 ] == 0 ) -- c.len; return t; }
t是余数,会爆int
幂次方
基本构造
观察题目发现最终结果只能是"2"或者"2(0)"
所以考虑在dfs边界设置为0和1,表示\(2^0,
2^1\) 什么时候需要带括号呢?1 = "2(0)"
联想快速幂:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ll ksm (ll a, ll b, int mod) { ll res = 1ll ; for (; b; b >>= 1 ) { if (b & 1 ) res = res * a % mod; a = a * a % mod; b >>= 1 ; } return res; } ll ksm (ll a, ll b, int mod) { if (! b) return 1ll ; if (b & 1 ) return ksm (a, b - 1 , mod) * a % mod; ll res = ksm (a, b >> 1 , mod); return res * res % mod; }
1999
Cantor 表
找规律的一题,先找出该项在杨辉三角里面的哪一行,然后判断奇偶
奇:从左下到右上递增, 偶:从右上到左下递增
找到行首,接下来一个for就可以
回文数
这是一道高精+模拟的一个思维好题
有几个坑点,第一个是传址调用,新建局部变量数组,要清空,第二个是传值调用尽量从a开始,而不是从a+1,第三个是十六进制,d[i]
= tmp - 10 + 'A'
不可以写d[i] = tmp - 10 + '0' + 'A'从ascil的角度来看,多了48
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 inline void dfs (int t, char *a) { char b[N]; strcpy (b, a); reverse (b, b + strlen (b)); if (strcmp (a, b) == 0 ) return (void )(flag2 = t - 1 ); if (t > LIM) return (void )(flag1 = 1 ); if (flag1 || flag2) return ; int c[N], len = strlen (a); memset (c, 0 , sizeof c); for (int i = 0 ; i < len; ++ i) { int tmp1, tmp2; if (a[i] <= '9' && a[i] >= '0' ) tmp1 = a[i] - '0' ; else tmp1 = a[i] - 'A' + 10 ; if (b[i] <= '9' && b[i] >= '0' ) tmp2 = b[i] - '0' ; else tmp2 = b[i] - 'A' + 10 ; c[i] = tmp1 + tmp2; } for (int i = 0 ; i < len; ++ i) if (c[i] >= n) c[i + 1 ] += c[i] / n, c[i] %= n; if (c[len]) ++ len; char d[N]; int len2 = len; for (int i = 0 ; i < len; ++ i) { int tmp = c[-- len2]; if (tmp <= 9 ) d[i] = tmp + '0' ; else d[i] = tmp - 10 + 'A' ; } return (void )(dfs (t + 1 , d)); }
旅行家的预算
贪心加模拟
由于数据量很小,我的做法是,开一个结构体,除了n个油站放进去,起点和终点也塞进去,以距起点的距离为第一关键字排个序
递归边界是,到达终点,
递归过程是,针对于每一个油站可去的范围内,如果下一个比当前油价便宜,那么只需要恰好到达下一站即可,如果下一个比当前贵,那么最优选项就是当前加满再去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 inline void dfs (int t, double money, double h) { if (t == n) return (void )(flag = 1 , res = min (res, money)); for (int i = t + 1 ; i <= n; ++ i) { double s = (a[i].d - a[t].d) / d_2; if (s > c) break ; if (h >= s) dfs (i, money, h - s); else { if (a[i].p >= a[t].p) dfs (i, money + (c - h) * a[t].p, c - s); else dfs (i, money + (s - h) * a[t].p, 0.0 ); } } return ; }
2000
计算器的改良
普通模拟,有点类似栈但不是栈,因为没有括号,没有乘除
我的思路是根据'='分成两段,分别处理,最后整合
考虑针对每一段,每遇到一个特殊符号就处理,减号flag=1,加号不管,也就是flag=0,遇到数字就把上次的符号弹出,处理,然后复原
细节有点多,考虑以下几种情况
3-a=3
//-0.000?
a+2a=6
//3a=6
5a=5
//a=inf?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 for (int i = 0 ; i <= pos; ++ i){ if ((s[i] >= 'A' && s[i] <= 'Z' ) || (s[i] >= 'a' && s[i] <= 'z' )) { x1 = s[i]; int now = tmp; if (now == 0 ) { if (flag) now = -1 , flag = 0 ; else now = 1 ; } else { if (flag) now = -now, flag = 0 ; else now = now; } l += now; tmp = 0 ; continue ; } if (s[i] == '+' || s[i] == '=' ) { if (flag == 1 ) sum1 -= tmp; else sum1 += tmp; flag = 0 ; tmp = 0 ; continue ; } else if (s[i] == '-' ) { if (flag == 1 ) sum1 -= tmp; else sum1 += tmp; flag = 1 ; tmp = 0 ; continue ; } else tmp = tmp * 10 + s[i] - '0' ; }
乘积最大
一眼区间dp,dfs也可以做,需要高精
由于需要区间操作,所以按照惯例我们都需要预处理一下,把区间转换为字符串就好了
我们设\(f_{i,t}\) 表示在前i个数中插入t个乘号可以表示的最大值,由于正常递推无法确定上一次在哪里分割,所以我们改变思路,枚举一个j,即枚举分割点,然后结束了
calc模拟了高精乘过程,maxx是针对两个字符串取最大值,比的时候记得从高位开始比
局部变量里面的数组(结构体)一定要初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 inline void func (int l, int r) { string w = "" ; for (int i = l; i <= r; ++ i) w += s[i]; return w; } for (int i = 1 ; i <= n; ++ i)for (int j = i; j <= n; ++ j) b[i][j] = func (i, j); inline string calc (string x, string y) { bigint a, b, c; memset (a.num, 0 , sizeof a.num); memset (b.num, 0 , sizeof b.num); memset (c.num, 0 , sizeof c.num); a.len = x.size (), b.len = y.size (); c.len = a.len + b.len; for (int i = 0 ; i < a.len; ++ i) a.num[i] = x[a.len - 1 - i] - '0' ; for (int i = 0 ; i < b.len; ++ i) b.num[i] = y[b.len - 1 - i] - '0' ; for (int i = 0 ; i < a.len; ++ i) for (int j = 0 ; j < b.len; ++ j) c.num[i + j] += a.num[i] * b.num[j]; for (int i = 0 ; i < c.len; ++ i) if (c.num[i] > 9 ) c.num[i + 1 ] += c.num[i] / 10 , c.num[i] %= 10 ; while (c.len > 1 && c.num[c.len - 1 ] == 0 ) -- c.len; string w = "" ; for (int i = c.len - 1 ; i >= 0 ; -- i) w += c.num[i] + '0' ; return w; } for (int t = 1 ; t <= k; ++ t){ for (int i = 1 ; i <= n; ++ i) { f[i][0 ] = b[1 ][i]; for (int j = 1 ; j <= i; ++ j) { string tmp = calc (f[j - 1 ][t - 1 ], b[j][i]); f[i][t] = maxx (f[i][t], tmp); } } }
单词接龙
很工整的dfs,与字符串结合细节较多
题意有几处不清楚的地方:
单词接龙,单词可以重复使用
匹配的时候,重合部分不可以和尾巴长度相等,但是重合部分可以和龙头长度相等
a.substr(start, len)
从字符串a的start位置开始,选出len个字符
a.find(str, start)
从字符串a的start位置开始,查找str,没有start,默认从头找,找不到返回a.npos(一般都是-1)
普通的find函数无法满足该题需要,考虑如下情况: 1 2 t: atoucheat, s[i]: tact w: t, pos: 1
t
出现了两次,但我们只需要在后半段,也就是子段可能出现的地方查找就可以了
故int pos = t.find(w, len - 1 - k)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 inline void dfs (string t, int len) { for (int i = 1 ; i <= n; ++ i) { if (v[i] == 2 ) continue ; int slen = s[i].length (); for (int k = 0 ; k < slen; ++ k) { string w = s[i].substr (0 , k + 1 ); int pos = t.find (w, len - 1 - k); if (pos != string::npos && pos + k + 1 == len && w != s[i]) { string p = t.substr (0 , len - 1 - k); ++ v[i]; dfs (p + s[i], len - k - 1 + slen); -- v[i]; } } } return (void )(res = max (res, len)); } for (int i = 1 ; i <= n; ++ i){ if (s[i][0 ] == ch[0 ]) { ++ v[i]; dfs (s[i], s[i].length ()); -- v[i]; } }
搜索