试题 A: 组队
- 题意:在给定的20个人中,分别选五个人放到五个不同位置上,使得最后权值最大
- 暴力就完事了,对个位置进行,并在搜索过程中记录最大值即可。
当然也可以五重循环暴力跑一下
#include<bits/stdc++.h> using namespace std; const int N = 1e5 + 7; const int mod = 1000000009; typedef long long ll; vector<int> id[7]; bitset<32> vis; int ans = 0; void dfs(int s, int value) { if(s == 6) { ans = max(ans, value); return; } for(int i = 0; i < id[s].size(); ++i) { if(!vis[i]) { vis[i] = 1; dfs(s + 1, value + id[s][i]); vis[i] = 0; } } } signed main() { for(int i = 1; i <= 20; ++i) { int tmp; scanf("%d", &tmp); for(int j = 1; j <= 5; ++j) { scanf("%d", &tmp); id[j].push_back(tmp); } } dfs(1, 0); cout << ans << endl;//490 return 0; }
试题 B: 年号字串
- 题意:将2019转换成符合题意的字符串
- 对应,对应 ... 对应。可以看出这是一道进制转换题,不过最低位不是,而是,所以在转换进制时要注意一点
- 也就是说,它的按权展开为:。其中:,而不是:
#include<bits/stdc++.h> using namespace std; const int N = 1e5 + 7; const int mod = 1000000009; typedef long long ll; signed main() { int n; cin >> n; string s = ""; while(n) { n -= 1;//注意这里 s += n % 26 + 'A'; n /= 26; }reverse(s.begin(), s.end()); cout << s << endl;//BYQ return 0; }
试题 C: 数列求值
- 题意:**求对应数列的最后**位
- 直接递推取余就好,求最后位,
#include<bits/stdc++.h> using namespace std; const int N = 1e5 + 7; const int mod = 10000; typedef long long ll; signed main() { ll a, b, c; a = b = c = 1; for(int i = 4; i <= 20190324; ++i) { ll tmp = c; c = (a + b + c) % mod; a = b; b = tmp; }cout << c << endl;//4659 return 0; }
试题 D: 数的分解
- 题意:将2019分解为三个不同的正整数有多少种方法
- 很明显的是暴力,但是因为题目说了:分解为三个不同的正整数,并且不计次序
- 所以设:,就不会有重复计数问题了,然后再跑一边暴力统计即可
#include<bits/stdc++.h> using namespace std; const int N = 1e5 + 7; const int mod = 1000000009; typedef long long ll; bool check(int x) { bool flag = 0; while(x) { if(x % 10 == 2 || x % 10 == 4) flag = 1; x /= 10; }return flag; } signed main() { ll ans = 0; for(int i = 1; i <= 673; ++i) {//673 * 3 = 2019 if(check(i)) continue; for(int j = i + 1; j <= 2019 - i; ++j) { if(check(j)) continue; for(int k = j + 1; k <= 2019 - i - j; ++k) { if(check(k)) continue; if(i + j + k == 2019) { ++ans; } } } }cout << ans << endl;//40785 return 0; }
试题 E: 迷宫
- 题意:输出最少步数下的最小字典序走法
- 看到最少步数就应该想到,然后又因为是最小的字典序走法,所以走的时候按字典序进行搜索,最后第一个到达终点的就是答案了
- 记录走法的时候可以开个回溯的数组进行复原步骤,这里就直接全部存下来了
#include<bits/stdc++.h> using namespace std; const int N = 1e5 + 7; const int mod = 1000000009; typedef long long ll; char G[64][64]; char step[] = {'D','L','R','U'}; int dx[] = {1, 0, 0, -1}; int dy[] = {0, -1, 1, 0}; bitset<64> vis[64]; struct node { int x, y; string now = ""; }; queue<node> q; bool check(int x, int y) { bool flag = 1; if(x > 30 || x < 1) flag = 0; if(y > 50 || y < 1) flag = 0; if(flag) { if(G[x][y] == '1') flag = 0; } return flag; }//判断合法性 signed main() { for(int i = 1; i <= 30; ++i) { for(int j = 1; j <= 50; ++j) { int tmp; scanf(" %c", &tmp); G[i][j] = tmp; } } q.push({1,1,""}); vis[1][1] = 1; while(!q.empty()) { int x = q.front().x, y = q.front().y; string now = q.front().now; if(x == 30 && y == 50) { cout << now << endl; break; } q.pop(); for(int i = 0; i < 4; ++i) { int tx = x + dx[i], ty = y + dy[i]; if(check(tx, ty) && !vis[tx][ty]) { vis[tx][ty] = 1; q.push({tx, ty, now + step[i]}); } } } return 0; } /*结果为:DDDDRRURRRRRRDRRRRDDDLDDRDDDDDDDDDDDDRDDRRRURRUURRDDDDRDRRRRRRDRRURRDDDRRRRUURUUUUUUULULLUUUURRRRUULLLUUUULLUUULUURRURRURURRRDDRRRRRDDRRDDLLLDDRRDDRDDLDDDLLDDLLLDLDDDLDDRRRRRRRRRDDDDDDRR 走了186步 */
试题 F: 特别数的和
- 题意:**给定数字,求中含数位的数字和为多少**
- 上来先看的范围,,数据比较小,加上数位分解的时间也完全够。
- 所以可以直接暴力,遍历,,对进行暴力分解,合法就加,否则继续
#include<bits/stdc++.h> using namespace std; const int N = 1e5 + 7; const int mod = 1000000009; typedef long long ll; bool check(int x) { bool flag = 0; while(x && !flag) { int tmp = x % 10; if(tmp == 0 || tmp == 1 || tmp == 2 || tmp == 9) flag = 1; x /= 10; }return flag; } signed main() { ll ans = 0; int n; scanf("%d", &n); for(int i = 1; i <= n; ++i) { if(check(i)) ans += i; }cout << ans << endl; return 0; }
试题 G:完全二叉树的权值
题意:给定一颗二叉树,求相同深度的权值和最大的深度编号,如果有多个,输出最小的深度
思路:对每一深度进行放入数组统计,排序可得最大权值和。并且值得注意的是有多个相同权值时,输出最小的深度。这里的处理是将其深度编号记录下来,但是如果不做任何改变就是求到了最大深度。所以需要将深度编号取反,才能在排序后将最小深度找到
并且二叉树的每一层有的节点数为:,此处依题意从开始
#include<bits/stdc++.h> using namespace std; const int N = 1e5 + 7; const int mod = 1000000009; typedef long long ll; int n, tot = 0; pair<ll,int> arr[64]; signed main() { scanf("%d", &n); int cnt = 1;//记录目前输入的个数 while(cnt <= n) { ll value = 0; for(int i = 1; i <= (1 << tot) && cnt <= n; ++i) { int tmp; scanf("%d", &tmp); value += tmp; ++cnt; } ++tot;//深度编号 arr[tot].first = value; arr[tot].second = -tot;//取编号最小的 } sort(arr + 1, arr + 1 + tot); cout << -arr[tot].second << endl; return 0; }
试题 H: 等差数列
- 题意:**给定长度为的数列,为一个等差数列的一部分。要使得等差数列项数最小,求出最小值**
- 假设给定的数列为:。并且设单增
- 我们要使得包含这些数的等差数列项数最小,就要使其公差尽可能的大,并且要包含所有的数字
- 所以数列可变形为:。其中为常数,设:
- 即:。我们要使公差尽可能大,那么就要尽可能小,也就是说,对于而言,我们要求出一个最大的,使得它在所有中都能分解出来。那么毫无疑问:
- 求出最大公差之后,由于,那么可得:
- 但是要注意一点:有可能为的情况(即为常数列),不能套上面的公式,此时最大项数就为
#include<bits/stdc++.h> using namespace std; const int N = 1e6 + 7; const int mod = 1000000009; typedef long long ll; ll gcd(ll a, ll b) {return b == 0 ? a : gcd(b, a % b);} int n; ll a[N], b[N]; signed main() { scanf("%d", &n); for(int i = 1; i <= n; ++i) scanf("%lld", a + i); sort(a + 1, a + 1 + n); for(int i = 2; i <= n; ++i) b[i] = a[i] - a[i-1]; ll d = b[2]; for(int i = 2; i <= n; ++i) d = gcd(d, b[i]); if(d) cout << (a[n] - a[1]) / d + 1 << endl; else cout << n << endl; return 0; }
试题 I: 后缀表达式
- 题意:**给定个加号,个减号,个数字,求最后运算结果的最大值**
- 我们知道,任意一个后缀表达式都可以与相应的带括号的中缀表达式相互转换。所以题目意思也就是说:求在带括号的中缀表达式下,最后运算结果的最大值。
- 首先,只要存在一个减号,就可以依靠:这种式子,将其转换为有且仅有一个减号的情况,并且可以拓展到存在任意个减号的情况。此时,可以看出与负数的数量相关性较大,所以我们来讨论一下(设负数数量为:)
- 时,所有的负数都可以转换为正数,并且所有多余的减号都可以依靠上述做法转换为正号。
- 时,所有的负数也都可以由上述式子转换为正数。
- 但是负号最少只有一个,当所有的数均为非负数时,必须减去一个最小的数,才能使得最后的结果最大
- 并且可以看到,我们默认了为非负值,但如果所有的值均为负数时,,那么为负无法改变,但其余的负数均可以改为正数。所以,直接选取当前序列中的最大值作为进行运算
- 反正,如果没有任何一个减号,则整个式子不论何种运算顺序,结果都是固定的。
#include<bits/stdc++.h> using namespace std; const int N = 1e6 + 7; const int mod = 1000000009; typedef long long ll; ll a[N], n, m; signed main() { scanf("%lld%lld", &n, &m); ll ne = 0;//统计负数个数 for(int i = 1; i <= n + m + 1; ++i) { scanf("%lld", a + i); if(a[i] < 0) ++ne; } sort(a + 1, a + 1 + n + m + 1); ll ans = 0; if(m) { ans = a[n + m + 1] - a[1];//选取当前最大值作为a_k for(int i = 2; i < n + m + 1; ++i) ans += abs(a[i]); }else { for(int i = 1; i <= n + m + 1; ++i) ans += a[i]; } cout << ans << endl; return 0; }
试题 J: 灵能传输
题意:**给定序列,可以任意的在非头尾处选择任意多次进行传输,最后使得最小**
阴间题目。可以看出,传输操作较为复杂,并且在传输过程中,总和不变。所以我们考虑转换为和的方向去看。假设序列为:。
如果对进行操作,则原序列变为:。
则前缀和变成了:,可以看出传输操作仅对进行了对换,比原来的传输操作更好操作。
所以题目就转换为了:**任意交换其中,任意多次。使得最后的序列中最小**
题目中,仅有不能移动,加入使得其同样不能移动,使得前缀和满足通式,且形成对称结构。
对于而言,可以任选一个作为起始,另一个作为终止位置,对题目没有任何影响。然后对前缀和的大小进行排序
假设:。并且先将填入起始、终止位置。
从开始,填入比小的元素。从开始,填入比大的数。最后再将二者由剩余元素衔接起来。并且在填入的基础上要使得相邻元素的差值的绝对值最小
那么如何使得其成立呢?可以看出,我们先处理了最值部分,在衔接时,我们需要在填入前预留一个数字,使得最值形成的谷、峰尽可能的对称。则谷、峰两侧相应位置的元素必然是相邻元素,这样才能使得两侧的差值不过大。
那么选取方法也就出来了:对于和的元素,隔一个取一个,并且分别在头、尾插入。然后标记哪些元素被选取,哪些未被选取,最后由小到大将头尾衔接起来即可
最后对生成的序列跑一遍,取最大值即可
#include<bits/stdc++.h> using namespace std; const int N = 1e6 + 7; const int mod = 1000000009; typedef long long ll; ll n, t; ll prefix[N], tmp[N]; bitset<N> vis; signed main() { scanf("%lld", &t); while(t--) { memset(prefix, 0, sizeof(ll) * (n + 1)); memset(tmp , 0, sizeof(ll) * (n + 1)); vis.reset(); scanf("%lld", &n); for(int i = 1; i <= n; ++i) scanf("%lld", &prefix[i]); for(int i = 1; i <= n; ++i) prefix[i] += prefix[i-1]; ll ans = 0, sn = prefix[n], s0 = 0; sort(prefix, prefix + 1 + n); if(sn < s0) swap(sn, s0); int l = 0, r = n;//头、尾指针 for(int i = lower_bound(prefix, prefix + 1 + n, s0) - prefix; i >= 0; i -= 2) tmp[l++] = prefix[i], vis[i] = 1;//< s_0 for(int i = lower_bound(prefix, prefix + 1 + n, sn) - prefix; i <= n; i += 2) tmp[r--] = prefix[i], vis[i] = 1;//> s_n for(int i = 0; i <= n; ++i) { if(!vis[i]) tmp[l++] = prefix[i];//衔接 } for(int i = 1; i <= n; ++i) { ans = max(ans, abs(tmp[i] - tmp[i-1])); } cout << ans << endl; } return 0; }