中南林业科技大学第十一届程序设计大赛 - 题解
题目链接:牛客同步赛。
现场赛榜单:
同步赛榜单:点这儿。
我出了本场比赛的H题、I题、J题、K题。其中H和I是把2018年校招的两道题拿过来给学弟学妹们做。
这次比赛的H题和I题数据水了,导致很多同学暴力的代码也过了,在此对那些没暴力的同学说声抱歉,下次数据一定好好检验;
C题是一个签到题,但是现场赛没有一个同学过这个题。
这次比赛也是我参与的最后一届校赛了,过几天就毕业了。希望学弟学妹们好好加油。
A - 译码
解析
这题要读清题意,3个字母3个字母地编码,不是一起编码,因此题中说了保证输入是5的倍数,然后模拟就好了。
代码
#include <bits/stdc++.h>
using namespace std;
int main()
{
int T, len;
cin >> T;
for (string s; T-- ;) {
cin >> len >> s;
string ret;
for (int src = 0; src < len; src += 5) {
string str = s.substr(src, 5), ans;
for (int i = 0; i < 3; i++) {
int sum = 0;
string tmp;
for (auto digit : str) {
sum = sum * 10 + digit - '0';
tmp.push_back(sum / 26 + '0');
sum %= 26;
}
ans.push_back(sum + 'a');
str.swap(tmp);
}
reverse(ans.begin(), ans.end());
ret += ans;
}
cout << ret << endl;
}
return 0;
}
B - Fence Repair
解析
签到题。
代码
#include <bits/stdc++.h>
using namespace std;
int main()
{
for (int n, ans = 0; cin >> n ; ans = 0) {
vector<int> arr(n);
for (int i = 0; i < n; cin >> arr[i++]) {}
for (auto it = arr.begin(); next(it) != arr.end(); ++it)
ans += count(next(it), arr.end(), *it << 1);
cout << ans << endl;
}
return 0;
}
C - 有趣的二进制
解析
其实这题也是个签到题,但是现场赛没有一个同学写出来。
算一个整数的二进制有多少个1,这个方法太多了,但是无论哪种方法,把输入的这个整数转化成无符号整数再做是最稳的,因为计算机中存储整数就是存储其补码形式,而转化成无符号整数不会使其二进制位发生变化,只是会让编译器对这串二进制的解释有所不同而已;
这里要用到long long
或者unsigned long long
,至于求二进制有多少个1的方法,见剑指Offer66题之每日6题 - 第二天的第五题。其实还有很多方法,我选了一种使用了std::bitset
的方法。
代码
#include <bits/stdc++.h>
using namespace std;
int main()
{
for (long long n; cin >> n ; cout << bitset<64>(n).count() << endl) {}
return 0;
}
D - 有趣的数字
解析
这个题找规律。手算或者自己用电脑打表可以发现答案就是。
代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main()
{
for (LL n; cin >> n; cout << n - (LL)sqrt(n) << endl) {}
return 0;
}
E - 邝博士的问题
解析
签到题。
代码
#include <bits/stdc++.h>
using namespace std;
int main()
{
for (int n; cin >> n; ) {
vector<int> arr(3);
for (int i = 0; i < 3; ++i)
arr[i] = ceil((i + 1) / 10.0 * n) - floor((i + 1) / 10.0 * n);
partial_sum(arr.begin(), arr.end(), arr.begin());
cout << arr[0] << " " << arr[1] << " " << arr[2] << endl;
}
return 0;
}
F - 新田忌赛马
解析
贪心就好。
首先把齐威王和田忌的马按速度分别排序;然后在齐威王的马中找出不能战胜田忌速度最小的所有马,并加入有限队列(优先队列中按齐威王的刀币值排序)。然后从优先队列中弹出一匹马(这匹马一定就是田忌这匹速度最小能战胜的并且能获得最大刀币值的马);然后又在齐威王的马中找出不能战胜田忌速度第二小的马,加入优先队列,这个时候,田忌速度第二小的马也一定能战胜那些不能战胜田忌速度最小的马,因此,直接从优先队列中弹出一匹马,依次类推;最后处理完田忌所有的马,这时优先队列中的马和没有入优先队列的马就是田忌要输给的马,减去这些刀币值就好了,这样做一定是优的(战胜那些刀币值较大的马,输给那些刀币值较小的马)。
代码
#include <bits/stdc++.h>
using namespace std;
int main()
{
int T;
cin >> T;
for (int n, ans = 0; cin >> n; ans = 0) {
vector<pair<int, int> > a(n);
vector<int> b(n);
for (int i = 0; i < n; cin >> a[i++].first) {}
for (int i = 0; i < n; cin >> a[i++].second) {}
for (int i = 0; i < n; cin >> b[i++]) {}
sort(a.begin(), a.end());
sort(b.begin(), b.end());
priority_queue<int> que;
auto it_pos = a.begin();
for (auto ele : b) {
for (; it_pos != a.end() && it_pos->first < ele; ++it_pos)
que.push(it_pos->second);
if (que.empty())
continue;
ans += que.top();
que.pop();
}
for (; it_pos != a.end(); que.push(it_pos->second), ++it_pos) {}
for (; !que.empty(); ans -= que.top(), que.pop()) {}
cout << ans << endl;
}
return 0;
}
G - 组合游戏
解析
贪心,每次选最短的两块模板组合,然后把新的木板放入候选模板,再做就是了,最后剩一块木板就结束了。
代码
#include <bits/stdc++.h>
using namespace std;
int main()
{
for (int n, ans = 0; cin >> n; ans = 0) {
auto cmp = [] (const int a, const int b) {
return a > b;
};
priority_queue<int, vector<int>, decltype(cmp)> que(cmp);
for (int i = 0, x; i < n; ++i)
cin >> x, que.push(x);
for (; que.size() != 1; ) {
int d1 = que.top();
que.pop();
int d2 = que.top();
que.pop();
que.push(d1 + d2);
ans += d1 + d2;
}
cout << ans << endl;
}
return 0;
}
H - 渴望力量吗
解析
这个题是今日头条2018校园招聘后端开发工程师(第二批)编程题 - 题解中的第一题,可以看看这个解析。使用的代码也在那篇博客中,这里给出自己手写这几个二分的代码。
代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#define MAX_SIZE (300000 + 5)
typedef struct tagNode {
int val, id;
}Node;
Node nodes[MAX_SIZE];
int cmp(const void *a, const void *b)
{
Node *pa = (Node *)a;
Node *pb = (Node *)b;
return pa->val == pb->val ? pa->id - pb->id :
pa->val - pb->val;
}
int main()
{
int n, m, k;
while (EOF != scanf("%d", &n)) {
int i, l, r, mid, L, R;
memset(nodes, 0, sizeof(nodes));
for (i = 0; i < n; i++)
scanf("%d", &nodes[i].val), nodes[i].id = i;
qsort(nodes, n, sizeof(nodes[0]), cmp);
scanf("%d", &m);
for (i = 0; i < m; i++) {
int left, right, lleft, rright;
scanf("%d%d%d", &L, &R, &k);
l = 0, r = n;
while (l < r) {
mid = (l + r) / 2;
if (nodes[mid].val < k)
l = mid + 1;
else
r = mid;
}
left = l;
l = -1, r = n - 1;
while (l < r) {
mid = (l + r + 1) / 2;
if (nodes[mid].val > k)
r = mid - 1;
else
l = mid;
}
right = l;
l = left, r = right + 1;
while (l < r) {
mid = (l + r) / 2;
if (nodes[mid].id < L - 1)
l = mid + 1;
else
r = mid;
}
lleft = l;
l = left - 1, r = right;
while (l < r) {
mid = (l + r + 1) / 2;
if (nodes[mid].id > R - 1)
r = mid - 1;
else
l = mid;
}
rright = l;
printf("%d\n", rright - lleft + 1);
}
}
return 0;
}
I - 背包问题
解析
这题可以参见网易2019实习生招聘编程题集合 - 题解中的第八题,中南大学第二届湖南地区程序设计邀请赛的A题也是这个,感觉好多地方都考察了。
J - are you ok?
解析
找第一个不小于K的数,然后对它前一个数加一。
前一部分可以用二分查找,维护区间[1, i]
的最大值arr[i]
,然后在arr[i]
上二分查找;
后一部分加一操作会打乱前面已经维护好的arr[i]
,注意这里只会打乱[1, i]
的arr
,因此这里用线段树维护。
代码
#include <bits/stdc++.h>
using namespace std;
struct Node {
int theMax;
Node *lchs, *rchs;
Node (int theMax = 0, Node *lchs = nullptr, Node *rchs = nullptr) :
theMax(theMax), lchs(lchs), rchs(rchs) {}
};
void build(int l, int r, vector<int> &arr, Node **root)
{
if (l == r) {
*root = new Node(arr[l - 1]);
return ;
}
*root = new Node;
build(l, (l + r) >> 1, arr, &((*root)->lchs));
build(((l + r) >> 1) + 1, r, arr, &((*root)->rchs));
(*root)->theMax = max((*root)->lchs->theMax, (*root)->rchs->theMax);
}
void fail(Node *root)
{
if (root == nullptr)
return ;
fail(root->lchs);
fail(root->rchs);
delete root;
}
int query(int l, int r, Node *root, int x)
{
if (l == r)
return root->theMax >= x ? l : l + 1;
return root->lchs->theMax >= x ? query(l, (l + r) >> 1, root->lchs, x) :
query(((l + r) >> 1) + 1, r, root->rchs, x);
}
void update(int l, int r, Node *root, int index)
{
if (l == r) {
++root->theMax;
return ;
}
int mid = (l + r) >> 1;
if (l <= index && index <= mid)
update(l, mid, root->lchs, index);
if (mid + 1 <= index && index <= r)
update(mid + 1, r, root->rchs, index);
root->theMax = max(root->lchs->theMax, root->rchs->theMax);
}
int main()
{
for (int m, n; /* cin >> m >> n */EOF != scanf("%d%d", &m, &n); ) {
vector<int> arr(m);
for (int i = 0; i < m; /* cin >> arr[i++] */scanf("%d", &arr[i++])) {}
Node *root = nullptr;
build(1, m, arr, &root);
for (int i = 0, x; i < n; ++i) {
// cin >> x;
scanf("%d", &x);
int index = query(1, m, root, x);
if (index == m + 1)
// cout << "are you ok" << endl;
puts("are you ok");
else
// cout << index - 1 << endl;
printf("%d\n", index - 1);
if (index == 1 || index == m + 1)
continue;
update(1, m, root, index - 1);
}
fail(root);
}
return 0;
}
但是,你再细心点就会发现,修改只会修改arr[i - 1]
,因此可以不用线段树维护。
#include <bits/stdc++.h>
using namespace std;
int main()
{
for (int m, n; /* cin >> m >> n */EOF != scanf("%d%d", &m, &n); ) {
vector<int> arr(m), theMax(m);
for (int i = 0; i < m; /* cin >> arr[i++] */scanf("%d", &arr[i++])) {}
partial_sum(arr.begin(), arr.end(), theMax.begin(), [] (int ans, int ele) {
return max(ans, ele);
});
for (int i = 0, x; i < n; ++i) {
// cin >> x;
scanf("%d", &x);
auto index = lower_bound(theMax.begin(), theMax.end(), x);
if (index == theMax.end())
// cout << "are you ok" << endl;
puts("are you ok");
else
// cout << index - 1 << endl;
printf("%d\n", index - theMax.begin());
if (index == theMax.end() || index == theMax.begin())
continue;
if (arr[index - theMax.begin() - 1] == *prev(index))
++*prev(index);
++arr[index - theMax.begin() - 1];
}
}
return 0;
}
K - 序列求和
解析
,这个题就只需要求6的逆元就行了;
这题本意是想让大家用矩阵快速幂做。参见深入浅出矩阵快速幂及其简单应用。
代码
// 逆元
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const LL MOD = 1000000007LL;
template<typename T>
T mul(T a, LL n, LL mod)
{
T ret(1);
for (; n; n >>= 1) {
ret = ret * (n & 1 ? a : T(1)) % mod;
a = a * a % mod;
}
return ret;
}
int main()
{
for (LL n; EOF != scanf("%lld", &n); ) {
LL ans = n % MOD * (n % MOD + 1) % MOD % MOD * (2 * (n % MOD) + 1) % MOD * mul(6LL, MOD - 2, MOD) % MOD;
printf("%lld\n", ans);
}
return 0;
}
// 矩阵快速幂
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const LL MOD = 1000000007LL;
template<typename T, int N = 1>
struct Matrix {
Matrix(int f = 0) : n(sizeof(data[0]) / sizeof(data[0][0])) {
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
data[i][j] = T(0);
if (f)
for (int i = 0; i < n; data[i][i] = T(1), ++i) {}
}
Matrix operator * (const Matrix& other) const {
Matrix<T, N> ret;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
for (int k = 0; k < n; k++)
ret.data[i][j] = (ret.data[i][j] + data[i][k] * other.data[k][j] % MOD) % MOD;
return ret;
}
Matrix operator + (const Matrix& other) const {
Matrix<T, N> ret;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
ret.data[i][j] = (data[i][j] + other.data[i][j]) % MOD;
return ret;
}
Matrix& operator % (const LL mod) {
return *this;
}
T data[N][N];
int n;
};
template<typename T>
T mul(T a, LL n, LL mod)
{
T ret(1);
for (; n; n >>= 1) {
ret = ret * (n & 1 ? a : T(1)) % mod;
a = a * a % mod;
}
return ret;
}
#define MAX_N 4
const LL modulu[MAX_N][MAX_N] = {
{1, 1, 0, 0},
{0, 1, 2, 1},
{0, 0, 1, 1},
{0, 0, 0, 1}
};
int main()
{
for (LL n; cin >> n; ) {
Matrix<LL, MAX_N> a;
memcpy(a.data, modulu, sizeof(modulu));
a = mul(a, n - 1, MOD);
cout << (a.data[0][0] + a.data[0][1] * 4 + a.data[0][2] * 2 + a.data[0][3]) % MOD << endl;
}
return 0;
}