2025牛客寒假算法基础集训营3
A. 智乃的博弈游戏
题意:两个人轮流拿石头,拿的数量必须和总数互质,轮到某个玩家时只剩下一颗石头就算他赢。问先手能不能赢。
如果
点击查看代码
void solve() {
i64 n;
std::cin >> n;
if (n & 1) {
std::cout << "Yes\n";
} else {
std::cout << "No\n";
}
}
B. 智乃的质数手串
题意:
考虑不是环而是一条链该怎么做。从左到右看,如果有个数可以删那就直接删,不删的话也不能让它删去右边的数,留着反而没用。那么可以用一个栈来模拟,贪心的删除每个数。
考虑是环该怎么做。经典的破环成链,把数组复制一份到后面,然后用双端队列操作,每次把和当前位置距离已经超过
点击查看代码
const int N = 2e5 + 5;
std::vector<int> primes;
bool st[N];
void init(int n) {
for (int i = 2; i <= n; ++ i) {
if (!st[i]) {
primes.push_back(i);
}
for (auto & p : primes) {
if (p * i > n) {
break;
}
st[p * i] = true;
if (i % p == 0) {
break;
}
}
}
}
void solve() {
init(2e5);
int n;
std::cin >> n;
std::vector<int> a(2 * n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
a[i + n] = a[i];
}
std::deque<int> dq;
int pos = -1;
for (int i = 0; i < 2 * n; ++ i) {
while (dq.size() && i - dq.front() >= n) {
dq.pop_front();
}
while (dq.size() && !st[a[i] + a[dq.back()]]) {
dq.pop_back();
}
if (i >= n - 1 && dq.empty()) {
pos = i - n + 1;
break;
}
dq.push_back(i);
}
if (pos == -1) {
std::cout << "No\n";
} else {
std::cout << "Yes\n";
std::vector<int> ans;
std::stack<int> stk;
for (int i = pos; i < pos + n; ++ i) {
while (stk.size() && !st[a[i] + a[stk.top()]]) {
ans.push_back(stk.top());
stk.pop();
}
stk.push(i);
}
ans.push_back(stk.top());
for (int i = 0; i < n; ++ i) {
std::cout << ans[i] % n << " \n"[i == n - 1];
}
}
}
C. 智乃的Notepad(Easy version) && D. 智乃的Notepad(Hard version)
题意:你要输入
显然我们应该让尽可能多的字符串共用前缀。模拟一棵字典树,可以发现我们按照字典树的
点击查看代码
template <class Info>
struct ST {
std::vector<std::vector<Info>> st;
ST(std::vector<Info> a) {
int n = a.size(), m = std::__lg(n) + 1;
st.assign(n, std::vector<Info>(m));
for (int i = 0; i < n; ++ i) {
st[i][0] = a[i];
}
for (int j = 1; j < m; ++ j) {
for (int i = 0; i + (1 << j - 1) < n; ++ i) {
st[i][j] = st[i][j - 1] + st[i + (1 << j - 1)][j - 1];
}
}
}
Info query(int l, int r) {
int lg = std::__lg(r - l + 1);
return st[l][lg] + st[r - (1 << lg) + 1][lg];
}
};
struct Info {
int max;
};
Info operator + (Info a, Info b) {
return {std::max(a.max, b.max)};
}
template <class T>
struct Fenwick {
int n;
std::vector<T> tr;
Fenwick(int _n) {
init(_n);
}
void init(int _n) {
n = _n;
tr.assign(_n + 1, T{});
}
void add(int x, const T &v) {
for (int i = x; i <= n; i += i & -i) {
tr[i] = tr[i] + v;
}
}
T query(int x) {
T res = 0;
for (int i = x; i; i -= i & -i) {
res = res + tr[i];
}
return res;
}
T sum(int l, int r) {
return query(r) - query(l - 1);
}
};
const int N = 1e6 + 5;
std::vector<std::vector<int>> g(N);
int last[N];
const int TrieN = 1e6 + 5;
int trie[TrieN][26];
struct Trie {
int idx;
Trie(int n) {
idx = 0;
}
int newNode() {
idx += 1;
memset(trie[idx], 0, sizeof trie[idx]);
return idx;
}
void insert(std::string s, int id) {
int p = 0;
for (auto & c : s) {
int x = c - 'a';
if (!trie[p][x]) {
trie[p][x] = newNode();
}
p = trie[p][x];
g[id].push_back(p);
}
}
};
void solve() {
int n, m;
std::cin >> n >> m;
int sum = 0;
std::vector<std::string> s(n + 1);
std::vector<Info> len(n + 1);
for (int i = 1; i <= n; ++ i) {
std::cin >> s[i];
len[i].max = s[i].size();
sum += s[i].size();
}
ST<Info> st(len);
Trie tr(sum);
for (int i = 1; i <= n; ++ i) {
tr.insert(s[i], i);
}
std::vector<std::vector<std::pair<int, int>>> Q(n + 1);
for (int i = 0; i < m; ++ i) {
int l, r;
std::cin >> l >> r;
Q[r].push_back({l, i});
}
Fenwick<int> tr1(n + 1);
std::vector<int> ans(m);
for (int i = 1; i <= n; ++ i) {
for (auto & j : g[i]) {
if (last[j]) {
tr1.add(last[j], -1);
}
tr1.add(i, 1);
last[j] = i;
}
for (auto & [j, id] : Q[i]) {
ans[id] = 2 * tr1.sum(j, i) - st.query(j, i).max;
}
}
for (int i = 0; i < m; ++ i) {
std::cout << ans[i] << "\n";
}
}
E.智乃的小球
题意:
两个球碰撞后可以看作是互相穿过了对方。因为它们交换了方向,然后他们的位置又是相邻的,完全可以看作两个小球都往自己的方向前进了。
那么我们可以二分时间,看每个球能和多少球相撞。
点击查看代码
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<i64> a, b;
for (int i = 0; i < n; ++ i) {
i64 p, v;
std::cin >> p >> v;
if (v == 1) {
a.push_back(p);
} else {
b.push_back(p);
}
}
std::sort(a.begin(), a.end());
std::sort(b.begin(), b.end());
n = a.size();
int m = b.size();
auto check = [&](double t) -> i64 {
i64 cnt = 0;
for (int i = 0, l = 0, r = 0; i < n; ++ i) {
while (l < m && b[l] < a[i]) {
++ l;
}
r = std::max(l, r);
while (r < m && b[r] <= a[i] + 2 * t) {
++ r;
}
cnt += r - l;
}
return cnt;
};
double l = 1, r = 1e9;
while (r - l > 1e-6) {
double mid = (l + r) / 2;
// std::cerr << mid << "\n";
if (check(mid) >= k) {
r = mid;
} else {
l = mid;
}
}
std::cout << std::fixed << std::setprecision(12);
if (check(r) < k) {
std::cout << "No\n";
return;
}
std::cout << "Yes\n";
std::cout << r << "\n";
}
F. 智乃的捉迷藏
题意:有六个位置,三个人看着。其中每个人都有一个位置只能自己看到。其他三个位置都会被某两个人看到。总共有
在能被两个位置看到的人会被算多次。那么人数最少为所有人都不在会在能被两个人看见的位置上,那么就是
点击查看代码
void solve() {
int n, a, b, c;
std::cin >> n >> a >> b >> c;
if (a + b + c < n || a + b + c > n * 2) {
std::cout << "No\n";
} else {
std::cout << "Yes\n";
}
}
G. 智乃与模数
题意:求所有的
对于一个区间
那么我们可以二分找前
这题思路大致就是这样,不过二分还是有点小细节,可能没有一个
点击查看代码
void solve() {
i64 n, k;
std::cin >> n >> k;
auto check = [&](int x) -> std::pair<int, i64> {
int cnt = 0;
i64 sum = 0;
for (int l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
int a = n % l, d = n / l;
if (a < x) {
continue;
}
//a - (i - 1) * d >= x
//i <= (a - x) / d + 1
i64 len = std::min(r - l + 1, (a - x) / d + 1);
cnt += len;
sum += len * (a + a - (len - 1) * d) / 2;
}
return {cnt, sum};
};
int l = 1, r = n / 2;
while (l < r) {
int mid = l + r >> 1;
if (check(mid).first <= k) {
r = mid;
} else {
l = mid + 1;
}
}
auto [cnt, sum] = check(l);
i64 ans = sum + (i64)(k - cnt) * (l - 1);
std::cout << ans << "\n";
}
H. 智乃与黑白树
题意:给你一棵树,每个点要么是黑的要么是白的。问所有是黑色开头白色结尾的路径长度之和。
这题的讨论还是有点麻烦,出题人的写法很好,以前没见过,感觉让换根dp变的好理解了,这里借鉴一下。
记
如果已经算出来了以
点击查看代码
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
std::vector<std::vector<std::pair<int, int> > > adj(n);
std::vector<std::pair<int, int> > edges(n);
for (int i = 1; i < n; ++ i) {
int u, v;
std::cin >> u >> v;
-- u, -- v;
adj[u].push_back({i, v});
adj[v].push_back({i, u});
edges[i] = {u, v};
}
std::vector cnt(n, std::array<i64, 2>{});
std::vector sum(n, std::array<i64, 2>{});
std::vector<i64> f(n);
auto link = [&](int u, int v) -> void {
f[u] += f[v] + sum[u][0] * cnt[v][1] + sum[u][1] * cnt[v][0] +
cnt[u][0] * (sum[v][1] + cnt[v][1]) + cnt[u][1] * (sum[v][0] + cnt[v][0]);
sum[u][0] += sum[v][0] + cnt[v][0];
sum[u][1] += sum[v][1] + cnt[v][1];
cnt[u][0] += cnt[v][0];
cnt[u][1] += cnt[v][1];
};
auto cut = [&](int u, int v) -> void {
cnt[u][1] -= cnt[v][1];
cnt[u][0] -= cnt[v][0];
sum[u][1] -= sum[v][1] + cnt[v][1];
sum[u][0] -= sum[v][0] + cnt[v][0];
f[u] -= f[v] + sum[u][0] * cnt[v][1] + sum[u][1] * cnt[v][0] +
cnt[u][0] * (sum[v][1] + cnt[v][1]) + cnt[u][1] * (sum[v][0] + cnt[v][0]);
};
auto dfs = [&](auto self, int u, int fa) -> void {
int x = s[u] == 'b';
cnt[u][x] = 1;
for (auto & [_, v] : adj[u]) {
if (v == fa) {
continue;
}
self(self, v, u);
link(u, v);
}
};
dfs(dfs, 0, -1);
std::vector<std::pair<i64, i64> > ans(n);
auto dfs1 = [&](auto self, int u, int fa) -> void {
for (auto & [id, v] : adj[u]) {
if (v == fa) {
continue;
}
cut(u, v);
if (u == edges[id].first) {
ans[id] = {f[u], f[v]};
} else {
ans[id] = {f[v], f[u]};
}
link(v, u);
self(self, v, u);
cut(v, u);
link(u, v);
}
};
dfs1(dfs1, 0, -1);
for (int i = 1; i < n; ++ i) {
std::cout << ans[i].first << " " << ans[i].second << "\n";
}
}
I. 智乃的兔子跳
题意:给你
以前没有接触过这种随机数的题。
点击查看代码
std::mt19937 gen(std::random_device{}());
int rand(int l, int r) {
std::uniform_int_distribution<int> dis(l, r);
return dis(gen);
}
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
if (n == 1) {
std::cout << a[0] << " " << 2 << "\n";
return;
}
auto get = [&](int p, int d) -> std::pair<int, int> {
int cnt = 0;
for (auto & x : a) {
if ((x - p) % d == 0) {
++ cnt;
}
}
return {cnt, p % d};
};
int ans = 2;
int p = 0, max = 0;
int t = 100;
while (t -- ) {
int i = rand(0, n - 1), j;
do {
j = rand(0, n - 1);
} while (i == j);
int k = std::abs(a[i] - a[j]);
if (k == 1) {
continue;
}
for (int d = 2; d * d <= k; ++ d) {
if (k % d == 0) {
while (k % d == 0) {
k /= d;
}
auto [cnt, pos] = get(a[i], d);
if (cnt > max) {
max = cnt;
p = pos;
ans = d;
}
}
}
if (k == 1) {
continue;
}
auto [cnt, pos] = get(a[i], k);
if (cnt > max) {
max = cnt;
p = pos;
ans = k;
}
}
std::cout << p << " " << ans << "\n";
}
J. 智乃画二叉树
题意:按要求画一棵二叉树。
感觉这题对我来说并不算难,写过蓝书上的搜索章节题目后,对于这种题还是得心应手的。不过赛时没看这题。。。
先算出最大高度和宽度,都是三百八十几,那么我们开个
点击查看代码
const int N = 400;
char a[N][N];
int son[100][2];
int in[100];
int len[10];
void draw_node(int h, int w, int u) {
a[h][w] = a[h][w + 1] = '_';
a[h - 1][w - 1] = '/'; a[h - 1][w + 2] = '\\';
if (u < 10) {
a[h - 1][w] = u + '0';
} else {
a[h - 1][w] = u / 10 + '0';
a[h - 1][w + 1] = u % 10 + '0';
}
a[h - 2][w - 1] = '\\'; a[h - 2][w + 2] = '/';
a[h - 2][w] = a[h - 2][w + 1] = '_';
}
void dfs(int h, int w, int u, int k) {
draw_node(h, w, u);
if (son[u][0] != -1) {
dfs(h - 3 * (1 << k - 1), w - len[k] / 2 - 1, son[u][0], k - 1);
int x = h - 3, y = w - 1;
while (x >= (h - 3 * (1 << k - 1))) {
a[x][y] = '/';
-- x, -- y;
}
}
if (son[u][1] != -1) {
dfs(h - 3 * (1 << k - 1), w + len[k] / 2 + 1, son[u][1], k - 1);
int x = h - 3, y = w + 2;
while (x >= (h - 3 * (1 << k - 1))) {
a[x][y] = '\\';
-- x, ++ y;
}
}
}
void solve() {
int n, k;
std::cin >> n >> k;
for (int i = 1; i <= n; ++ i) {
std::cin >> son[i][0] >> son[i][1];
if (son[i][0] > 0) {
++ in[son[i][0]];
}
if (son[i][1] > 0) {
++ in[son[i][1]];
}
}
len[1] = 4;
for (int i = 2; i <= k; ++ i) {
len[i] = (len[i - 1] + 1) * 2;
}
int w = len[k];
int h = 3 * (1 << k - 1);
int root = 0;
for (int i = 1; i <= n; ++ i) {
if (!in[i]) {
root = i;
break;
}
}
for (int i = 0; i < N; ++ i) {
for (int j = 0; j < N; ++ j) {
a[i][j] = ' ';
}
}
dfs(N - 5, N / 2, root, k - 1);
int u = 0, d = N, l = N, r = 0;
for (int i = 0; i < N; ++ i) {
for (int j = 0; j < N; ++ j) {
if (a[i][j] != ' ') {
u = std::max(u, i);
d = std::min(d, i);
l = std::min(l, j);
r = std::max(r, j);
}
}
}
-- l, ++ r, ++ u, -- d;
for (int i = l; i <= r; ++ i) {
a[u][i] = a[d][i] = '*';
}
for (int i = d; i <= u; ++ i) {
a[i][l] = a[i][r] = '*';
}
for (int i = u; i >= d; -- i) {
for (int j = l; j <= r; ++ j) {
std::cout << a[i][j];
}
std::cout << "\n";
}
}
K. 智乃的逆序数
题意:给你
因为每个数组的数连续并且不会其他数组有相同的元素,那么这些数组从小到大排序后,逆序对数就等于每个数组的逆序对之和。因为前面数组的每个数一定比后面的任何一个数小,所以只需要计算每个数组的逆序对。然后我们模拟冒泡排序增加逆序对即可,注意不能和同一个数组的数交换。
点击查看代码
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<std::vector<int> > a(n);
for (int i = 0; i < n; ++ i) {
int m;
std::cin >> m;
while (m -- ) {
int x;
std::cin >> x;
a[i].push_back(x);
}
}
std::sort(a.begin(), a.end());
std::vector<std::pair<int, int> > ans;
for (int i = 0; i < n; ++ i) {
for (auto & x : a[i]) {
ans.push_back({x, i});
}
}
n = ans.size();
for (int i = 0; i < n; ++ i) {
for (int j = i + 1; j < n; ++ j) {
if (ans[i].first > ans[j].first) {
-- k;
}
}
}
if (k < 0) {
std::cout << "No\n";
return;
}
for (int i = 0; i < n; ++ i) {
int j = i;
while (j > 0 && k > 0 && ans[j].second != ans[j - 1].second && ans[j].first > ans[j - 1].first) {
-- k;
std::swap(ans[j], ans[j - 1]);
-- j;
}
}
if (k > 0) {
std::cout << "No\n";
return;
}
std::cout << "Yes\n";
for (int i = 0; i < n; ++ i) {
std::cout << ans[i].first << " \n"[i == n - 1];
}
}
L. 智乃的三角遍历
题意:找一条路线恰好经过一次图形的所有边。这个图形是一个
可以自己找一下方法,也可以打表。
我是每次从
点击查看代码
void solve() {
int n;
std::cin >> n;
std::cout << "Yes\n";
std::cout << 1 << " ";
int x = 1, k = 1;
for (int i = 1; i <= n; ++ i) {
// std::cout << i << "-----\n";
while (k <= n) {
x += k;
std::cout << x << " ";
k += 1;
}
k = n + 1;
std::cout << x + 1 << " ";
x += 1;
while (k > i + 1) {
std::cout << x - k << " " << x - k + 1 << " ";
x -= k - 1;
-- k;
}
k = i + 1;
// std::cout << "\n";
}
k = n + 1;
while (k > 1) {
x -= k;
std::cout << x << " ";
-- k;
}
}
M. 智乃的牛题
题意:判断给出的字符串能不能组成"
排序比较是不是就行,比赛时没看见给出的字符串长度为8,写了个看是不是子序列的代码。
点击查看代码
void solve() {
std::string s;
std::cin >> s;
std::string t = "nowcoder";
std::sort(t.begin(), t.end());
std::sort(s.begin(), s.end());
int j = 0;
for (int i = 0; j < t.size() && i < s.size(); ++ i) {
if (s[i] == t[j]) {
++ j;
}
}
if (j == t.size()) {
std::cout << "happy new year\n";
} else {
std::cout << "I AK IOI\n";
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战