www.codefun2000.com 是一个收集各种笔试题目,供大家学习交流,体会笔试难度的OJ网站
又是惨痛的一晚。
第一题
思路
在比赛时我的想法是用数学的方法:对于n位编号而言,包含5,20和520的编号数量是恒定的,可以通过排列组合算法将其数量分别计算出来,不过具体处理的时候,还是有蛮多细节,例如处理重复和处理边界,我就放下没做了。(应该是可以做出来的,后续再更新)
下面的做法是我赛后看的一位大佬的答案。这是一个蛮力的算法,确实在比赛和笔试过程中,如果没有很明确的思路,应该尽早尝试蛮力做法。细节是大佬做了一个优化,使用前缀和来减少重复计算。上面说到的数学方法也同样可以使用前缀和进行优化。
蛮力做法的时间复杂度是,数学做法的时间复杂度是,n是最大编号,m是最大编号的位数。
code
#include<bits/stdc++.h>
using namespace std;
// 记录前缀和,在编号小于等于i的编号中,包含5的编号数量是pre[0][i],
// 包含20的数量是pre[1][i],包含520的数量是pre[2][i]
int pre[3][1000010] = { 0 };
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
// 遍历所有编号
for (int i = 1; i <= 1000000; i++) {
string s = to_string(i);
int n = s.size();
int r1 = 0, r2 = 0, r3 = 0;
// 遍历编号 i 的所有位
for (int j = 0; j < n; j++) {
// j 位对应数字是 5
if (s[j] == '5')
r1 = 1;
// j 位对应数字是 2
if (s[j] == '2') {
if (j < n - 1 && s[j + 1] == '0') {
r2 = 1;
if (j > 0 && s[j - 1] == '5')
r3 = 1;
}
}
}
pre[0][i] += r1;
pre[1][i] += r2;
pre[2][i] += r3;
}
for (int i = 0; i < 3; i++) {
for (int j = 1; j <= 1000000; j++)
pre[i][j] += pre[i][j - 1];
}
int T;
cin >> T;
while(T--) {
int l, r;
cin >> l >> r;
--l;
cout << pre[0][r] - pre[0][l] << " " << pre[1][r] - pre[1][l] << " "
<< pre[2][r] - pre[2][l] << endl;
}
return 0;
}
第二题
思路
本题使用贪心算法,每次都直接投喂当前素质值最低的鸡。我们可以用一个小根堆简单解决,将当前素质值最低的鸡也就是小根堆的根节点取出,加上直接投喂增加的素质值后再重新加入队列,循环以上过程直至当前素质最差的鸡的素质值也已经大于目标素质值。
但有一个问题是应该如何处理间接投喂?
如果给最小堆的每个元素逐一加上间接投喂增加的素质值,代价是很高的。我在这里采用了一个巧妙的思想——“大家都胖了,就相当于没胖”,直接投喂的鸡素质值增加了,间接投喂的鸡素质值增加了(),那就等价于目标素质值降低,直接投喂的鸡素质值增加了,间接投喂的鸡素质值不变,如此一来也就无需逐一处理间接投喂的鸡了。
code
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int N, M, A, B;
cin >> N >> M >> A >> B;
vector<int> H(N);
for(auto& i:H) cin >> i;
// 创建一个小根堆
priority_queue<int, vector<int>, greater<int>> pq{H.begin(), H.end()};
int res = 0;
// 当小根堆的根节点大于等于目标素质值时,退出循环
while (pq.top() < M) {
// 直接投喂的鸡素质值增加 A-B,并重新加入队列
int top = pq.top() + (A-B);
pq.pop();
pq.push(top);
//
M -= B;
res++;
}
cout << res << endl;
return 0;
}
第三题
思路
待更新...
解决大数量测试用例的思路都是类似的。
code
第四题
思路
这题对于我来说有几个难点:
难点一:要想到将题目转化为图论问题
这题与引擎模块初始化有相似之处,都是需要处理某种依赖。
在本题中,我们使用一个有向图来表示角色间的胜负关系,两个角色间进行了战斗则有一条边,由输者指向胜者(打平的情况再难点三中讨论)。
应该怎么遍历这个图呢?
分等级其实就是分层,自然想到用BFS来处理。
应该如何判断战斗结果能不能完全决定等级设定呢?
比赛时,我的想法是
难点二:如何将胜负关系存储为一张图
在比赛时,我选择了邻接矩阵来存储,赛后看了2333大佬的答案,他用了vector<vector<int>> g来记录图关系,g[i]数组包含了角色 i 所有的邻居节点。这样的处理在后续BFS时处理起来更方便,时间复杂度也更低。
难点三:如何处理打平的情况
要解决这个问题,关键在于使得打平的情况也能在
在比赛的时候,我思考了几种方案,例如将
2333大佬使用了并查集,将打平的角色都放置于同一个集合中,意为二者等级应该相同。这个数据结构学完之后就没用过,刚好趁此机会复习一波。
code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int>;
void solve() {
int n, m;
cin >> n >> m;
vector<pii> e;
// 并查集
vector<int> root(n + 1);
// 初始时,每个角色自己为一个组
for (int i = 1; i <= n; i++) {
root[i] = i;
}
// 并查集查找操作
function<int(int)> find = [&](int x) -> int {
if (root[x] != x) root[x] = find(root[x]);
return root[x];
};
for (int i = 1; i <= m; i++) {
int a, b;
char c;
cin >> a >> c >> b;
if (c == '=') {
// 并查集合并操作,a和b角色属于同一组
root[find(a)] = find(b);
} else {
if (c == '<') swap(a, b);
e.push_back({a, b});
}
}
// 入度
vector<int> in(n + 1);
// 邻接矩阵
vector<vector<int>> g(n + 1);
for (pii &p : e) {
p.first = find(p.first);
p.second = find(p.second);
g[p.first].push_back(p.second);
in[p.second]++;
}
// 记录共有几个组(分组数)
// 如果等级设定成立,则tot就是需要设置的等级数量
int tot = 0;
// 记录遍历节点的数量
int cnt = 0;
queue<int> q;
for (int i = 1; i <= n; i++) {
if (find(i) != i) continue;
tot++;
// 将入度为0的角色作为bfs的起始节点
if (in[i] == 0) {
q.push(i);
}
}
// dp[i] 表示 角色 i 的等级
vector<int> dp(n + 1);
// BFS
while (!q.empty()) {
int node = q.front();
q.pop();
dp[node]++;
cnt++;
for (int nxt : g[node]) {
dp[nxt] = max(dp[nxt], dp[node]);
if (--in[nxt] == 0) {
q.push(nxt);
}
}
}
// 如果遍历节点的数量不等于分组数 或者
// 所有的角色等级中没有与分组数相同的
// 就意味着失败
if (cnt != tot || *max_element(dp.begin(), dp.end()) != tot) {
cout << -1 << endl;
} else {
cout << tot << endl;
}
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
solve();
}
}