2023牛客寒假算法基础集训营3 A-I+K

比赛链接

A

题解

知识点:贪心。

把所有正偶数除成奇数,即可。

(人傻了没加 x>0 WA2

时间复杂度 O(n)

空间复杂度 O(1)

代码

#include <bits/stdc++.h>
using ll = long long;
using namespace std;
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
ll ans = 0;
for (int i = 1;i <= n;i++) {
int x;
cin >> x;
while (x > 0 && x % 2 == 0) x /= 2;
ans += x;
}
cout << ans << '\n';
return 0;
}

B

题解

知识点:数学,构造。

特判 n=2 无解。

可以先放边长为 n2 正方形,随后边长每增加 1 需要最少 3 块,直到边长为 2n2 后,边长每增加 1 需要最少 5 块。以此类推,当边长为 [(k1)n2,kn2),kN+ 时,边长每增加 1 需要 2k1 块积木。

显然,摆完第一轮边长为 n2 后,剩下的 nn2 个积木,而 nn23n2 ,因此不可能摆到需要 5 个积木的情况。

综上,边长最大值为 n2+nn23

本题也可以用二分边长做。

(没考虑 nn2 大小,傻了吧唧的算了通式,不过可以出题了qwq

考虑 n 块积木,给定 m ,每块积木大小为 1×k,k[1,m2] ,求能摆成正方形的边长最大值。

时间复杂度 O(1)

空间复杂度 O(1)

代码

#include <bits/stdc++.h>
using ll = long long;
using namespace std;
bool solve() {
ll n;
cin >> n;
if (n == 2) return false;
ll a = (n + 1) / 2;
ll ans = a + (n - a) / 3;
cout << ans << '\n';
return true;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << -1 << '\n';
}
return 0;
}

C

题解

知识点:构造。

n3n=7 时无解。

考虑 nmod4=0,1,2,3 的情况。

  1. nmod4=0 时显然形如构造 3,4,1,2 的循环即可。
  2. nmod4=1 时,前 5 项构造成 4,5,1,2,3 ,其余仿照 nmod4=0 情况。
  3. nmod4=2 时,前 6 项构造成 4,5,6,1,2,3 ,其余仿照 nmod4=0 情况。
  4. nmod4=3 时,前 11 项构造分为 5 项和 6 项两组仿照 nmod4=1,2 情况,其余仿照 nmod4=0 情况。

时间复杂度 O(n)

空间复杂度 O(1)

代码

#include <bits/stdc++.h>
using ll = long long;
using namespace std;
bool solve() {
int n;
cin >> n;
if (n <= 3 || n == 7) return false;
int m = 1;
if (n % 4 == 1) {
cout << "4 5 1 2 3" << ' ';
m = 6;
}
else if (n % 4 == 2) {
cout << "4 5 6 1 2 3" << ' ';
m = 7;
}
else if (n % 4 == 3) {
cout << "4 5 1 2 3 9 10 11 6 7 8" << ' ';
m = 12;
}
for (int i = m;i <= n;i += 4) {
cout << i + 2 << ' ' << i + 3 << ' ' << i << ' ' << i + 1 << ' ';
}
cout << '\n';
return true;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
//cin >> t;
while (t--) {
if (!solve()) cout << -1 << '\n';
}
return 0;
}

D

题解

知识点:博弈论。

这类题需要先对局面分类,每种局面考虑找到一组平衡的操作,即对于其中一人,无论另一人如何操作,他都可以在下一次操作后回到原来的局面。

考虑将 n 分奇偶情况:

  1. n 为偶数,小红每次可以选 1 ,随后数变为奇数局面,小紫只有奇数因子能选,数又变为偶数局面。到最后,必然是小紫让数变为 0 ,因为只有小紫能让数变为偶数。因此,偶数局面小红必胜。
  2. n 为奇数,根据 n 为偶数的推理,发现奇数局面小红必败。

时间复杂度 O(1)

空间复杂度 O(1)

代码

#include <bits/stdc++.h>
using ll = long long;
using namespace std;
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
ll n;
cin >> n;
if (n & 1) cout << "yukari" << '\n';
else cout << "kou" << '\n';
return 0;
}

E

题解

知识点:计算几何。

A(xA,yA),B(xB,yB),C(xC,yC) 构成等腰直角三角形,其中 C 为顶点且在 AB 右侧,满足方程:

{xC+yC=xA+yBxCyC=xByA

方程可以通过全等三角形证明。

显然 CC 关于 AB 的对称点同时是或不是整数点,解出 C(xC,yC) 后判断是否为整数即可。

(平面几何永远的痛,并且以为无解输出-1收获WA

时间复杂度 O(1)

空间复杂度 O(1)

代码

#include <bits/stdc++.h>
using ll = long long;
using namespace std;
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
ll a, b, c, d;
cin >> a >> b >> c >> d;
ll x = a + d + c - b;
ll y = a + d + b - c;
if (x & 1 || y & 1)cout << "No Answer!" << '\n';
else cout << x / 2 << ' ' << y / 2 << '\n';
return 0;
}

F

题解

知识点:宇宙的终极答案。

通过你高超的中文流读取技术,发现这是营销号特有的文案。

本打算对此嗤之以鼻的你,阅读完样例后逐渐理解了一切,确信 42 就是宇宙的终极答案。

时间复杂度 O()

空间复杂度 O()

代码

#include <bits/stdc++.h>
using ll = long long;
using namespace std;
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cout << 42 << '\n';
return 0;
}

G

题解

知识点:模拟,枚举,dfs。

很简单(痛苦)的模拟。

dfs枚举每个 ? 的三种可能即可,注意快速幂前把底数模一下,因为可能炸 long long

可以选择预处理数字后边枚举边求值,也可以考虑枚举完再求值。注意,边枚举边求值不太适用于有优先级表达式。

(被表达式求值整了一顿,码力太差了QAQ

时间复杂度 O(312n)

空间复杂度 O(n)

代码

边枚举边求值

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll qpow(ll a, ll k, ll P) {
ll ans = 1;
while (k) {
if (k & 1) ans = ans * a % P;
k >>= 1;
a = a * a % P;
}
return ans;
}
int ans;
vector<int> num;
vector<char> op(20);
bool dfs(int step = 1, ll cur = num[0]) {
if (step == num.size()) return cur == ans;
op[step] = '+';
if (dfs(step + 1, cur + num[step])) return true;
op[step] = '-';
if (dfs(step + 1, cur - num[step])) return true;
if (cur > 0 && num[step] > 0) {
op[step] = '#';
if (dfs(step + 1, qpow(cur % num[step], cur, num[step]))) return true;
}
return false;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
string s;
cin >> s;
for (int i = 0;i < s.size();i++) {
if (isdigit(s[i])) ans = ans * 10 + s[i] - '0';
else num.push_back(ans), ans = 0;
}
if (dfs()) {
cout << num[0];
for (int i = 1;i < num.size();i++) cout << op[i] << num[i];
cout << '=' << ans << '\n';
}
else cout << -1 << '\n';
return 0;
}

枚举完求值,用到表达式计算。

这里给了一个模板,可以修改map的优先级,支持带括号的二元运算,以及伪负号运算(指负号运算必须打括号)。

#include <bits/stdc++.h>
using ll = long long;
using namespace std;
ll qpow(ll a, ll k, ll P) {
ll ans = 1;
while (k) {
if (k & 1) ans = ans * a % P;
k >>= 1;
a = a * a % P;
}
return ans;
}
map<char, int> mp = { {'+',0},{'-',0},{'#',0},{'=',0} };
bool calc(string s) {
vector<ll> num = { 0 };
vector<char> op;
for (auto ch : s) {
if (ch >= '0' && ch <= '9') num.back() = num.back() * 10 + ch - '0';
else {
while (op.size() && mp[ch] <= mp[op.back()]) {
char ope = op.back();
op.pop_back();
ll x = num.back();
num.pop_back();
if (ope == '+') num.back() += x;
else if (ope == '-') num.back() -= x;
else if (ope == '#') {
if (x <= 0) return false;
num.back() = qpow(num.back() % x, num.back(), x);
}
}
if (ch == '#' && num.back() <= 0) return false;
op.push_back(ch);
num.push_back(0);
}
}
return num[0] == num[1];
}
string s;
bool dfs(int step = 0) {
if (step == s.size()) return calc(s);
if (s[step] == '?') {
s[step] = '+';
if (dfs(step + 1)) return true;
s[step] = '-';
if (dfs(step + 1)) return true;
s[step] = '#';
if (dfs(step + 1)) return true;
s[step] = '?';
}
else {
while (step < s.size() && s[step] != '?') step++;
if (dfs(step)) return true;
}
return false;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> s;
if (dfs()) cout << s << '\n';
else cout << -1 << '\n';
return 0;
}

H

题解

知识点:概率dp。

可能重要的前置知识(大佬请跳过)

对于一维期望dp,在第 i 步时,其向其他状态转移的起点只有 fi 一种,因此在第 i 步起点是状态 fi 的概率是百分百的,变化的期望直接加就行。

例如,有期望状态 fi 。操作有两种,概率分别为 14,34,两种操作的贡献分别是 1,3 。那么可以有转移方程:

fi+1=14(fi+1)+34(fi+3)

但是,如果在一维期望dp的基础上,设每一步都有多个不同的状态,那么转移时的期望就不是简单加法了。

具体的说,在第 i 步时,其向其他状态转移的起点如果是 fi,jj 种,那么显然这 j 种状态都有概率成为起点,满足概率的总和为百分百。因此考虑 fi,j 为起点做转移时,变化的期望需要乘上其作为起点的概率,表示这步操作在 fi,j 作为起点的概率下的期望。当然,期望 fi,j 本身不需要再乘一遍概率,因为求这个期望时已经考虑了到这个状态的概率,同时我们还可以知道这 j 种期望的总和就是第 i 步的总期望。

例如,有期望状态 fi,0/1/2 ,设 fi,j 的概率为 gi,j 。操作有两种,概率分别为 14,34 ,我们假设从 fi,0/2 都可以通过两种操作转移到 fi+1,0 ,两种操作的贡献对于两种状态分别是 1,25,6 。那么对于 fi+1,0 可以有转移方程:

fi+1,0=14(fi,0+1gi,0)+34(fi,0+2gi,0)+14(fi,2+5gi,2)+34(fi,2+6gi,2)

接下来就可以轻松(真的吗)做这道题了。

fi,j,k 为执行到第 i 步且满足串首状态为 j 、串尾状态为 k 的期望个数。其中,j=0/1 表示串首是 rededrk=0/1 同理。

gi,j,k 为对应的 fi,j,k 发生的概率。注意,除了四种串首尾的状态,还有一种空串的状态,这里没有标记到数组里,但是每步还是得自己手动加上去的,我们记 prob 为空串的概率,空串的期望为 0 不需要考虑。

因此有转移方程:

{fi+1,0,0=13((0+1prob)+(fi,0,0+1gi,0,0)+(fi,0,1+1gi,0,1))+130+13(10fi,0,0)fi+1,0,1=130+13((fi,0,0+0gi,0,0)+(fi,0,1+1gi,0,1))+13(10fi,0,1)fi+1,1,0=13((fi,1,0+1gi,1,0)+(fi,1,1+1gi,1,1))+130+13(10fi,1,0)fi+1,1,1=130+13((0+0prob)+(fi,1,0+0gi,1,0)+(fi,1,1+1gi,1,1))+13(10fi,1,1+9gi,1,1)gi+1,0,0=13(prob+gi,0,0+gi,0,1)+130+13gi,0,0 gi+1,0,1=130+13(gi,0,0+gi,0,1)+13gi,0,1 gi+1,1,0=13(gi,1,0+gi,1,1)+130+13gi,1,0 gi+1,1,1=130+13(prob+gi,1,0+gi,1,1)+13gi,1,1 prob=13prob

写的很详细了,三个 13 对应三种操作,分别算一下概率和期望转移就行。特别注意,fi+1,1,1 的操作三转移可以产生十倍加九的期望。

代码用滚动数组压缩了一维, fj,k,0/1 代表第 i 步的各种概率/期望, gj,k,0/1 代表第 i+1 步的各种概率/期望。并且,代码转移时是用子状态刷表,而非如上述转移方程填表,因为写起来比较方便。填表也能写,本质都是一样的,很好理解。

推荐使用 Modint 不然开 long long 也救不了打 % 打到手酸。

时间复杂度 O(k)

空间复杂度 O(1)

代码

#include <bits/stdc++.h>
using ll = long long;
using namespace std;
const int P = 1e9 + 7;
struct Modint {
int val;
Modint(int _val = 0):val(_val %P) { format(); }
Modint(ll _val):val(_val %P) { format(); }
//if val in [-P,2P)
//maybe slower than global version
Modint &format() {
if (val < 0) val += P;
if (val >= P) val -= P;
return *this;
}
Modint inv()const { return qpow(*this, P - 2); }
Modint &operator+=(const Modint &x) { val += x.val;return format(); }
Modint &operator-=(const Modint &x) { val -= x.val;return format(); }
Modint &operator*=(const Modint &x) { val = 1LL * val * x.val % P;return *this; }
Modint &operator/=(const Modint &x) { return *this *= x.inv(); }
friend Modint operator-(const Modint &x) { return { -x.val }; }
friend Modint operator+(Modint a, const Modint &b) { return a += b; }
friend Modint operator-(Modint a, const Modint &b) { return a -= b; }
friend Modint operator*(Modint a, const Modint &b) { return a *= b; }
friend Modint operator/(Modint a, const Modint &b) { return a /= b; }
friend Modint qpow(Modint a, ll k) {
Modint ans = 1;
while (k) {
if (k & 1) ans = ans * a;
k >>= 1;
a = a * a;
}
return ans;
}
friend istream &operator>>(istream &is, Modint &x) {
ll _x;
is >> _x;
x = { _x };
return is;
}
friend ostream &operator<<(ostream &os, const Modint &x) { return os << x.val; }
};
/*
f[0/1][0/1][0]:概率
f[0/1][0/1][1]:期望
00 red-red
01 red-edr
10 edr-red
11 edr-edr
注意还有一种空串情况
需要每次操作前手动加
*/
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int k;
cin >> k;
array<array<array<Modint, 2>, 2>, 2> f = {};
Modint inv3 = Modint(3).inv();
Modint prob = 1;
for (int i = 1;i <= k;i++) {
//prod指空串概率,空串期望始终为0不需要管
array<array<array<Modint, 2>, 2>, 2> g = {};
g[0][0][0] = inv3 * prob;
g[1][1][0] = inv3 * prob;
g[0][0][1] = inv3 * prob;
//空串到g[1][1]的期望还是0,不用管
for (auto i : { 0,1 }) {
for (auto j : { 0,1 }) {
g[i][0][0] += inv3 * f[i][j][0];//操作1后的概率
g[i][1][0] += inv3 * f[i][j][0];//操作2后的概率
g[i][j][0] += inv3 * f[i][j][0];//操作3后的概率
g[i][0][1] += inv3 * f[i][j][1];//操作1后的期望
g[i][0][1] += inv3 * f[i][j][0];
g[i][1][1] += inv3 * f[i][j][1];//操作2后的期望
if (j == 1) g[i][1][1] += inv3 * f[i][j][0];//只有?1才能加
g[i][j][1] += 10 * inv3 * f[i][j][1];//操作3后的期望
if (i == 1 && j == 1) g[i][j][1] += 9 * inv3 * f[i][j][0];//只有11能加9个
}
}
prob *= inv3;
f = g;
}
Modint sum = 0;
for (auto i : { 0,1 })for (auto j : { 0,1 }) sum += f[i][j][1];
cout << sum << '\n';
return 0;
}

I

题解

知识点:数论,构造。

  1. 偶数情况,若 x1 是素数构造 n=(x1)2 ,则 f(n)=1+x1=x ; 若 x3 是素数构造 n=2(x3) ,则 f(n)=1+2+x3=x

  2. 奇数情况,因为一定存在 1 因子,我们考虑使其他因子的和凑出一个偶数 x1 。考虑最简单的素数情况,因为哥德巴赫猜想,一个大于等于 4 的偶数可以被分解成两个素数之和,我们只要找到两个不同的素数 p,q 使得 p+q=x1 ,那么构造 n=pq ,则 f(n)=1+p+q=x

    注意,哥德巴赫猜想所述是两个素数之和,不是两个不同的素数。经过测试 int 范围内,大于等于 8 的偶数都可以被分解为两个不同的素数之和,因此大于等于 9 的奇数我们无脑无解即可。

    我们需要特判 x=1,3,7 ,因为这些情况确实有解,但不能通过哥德巴赫猜想构造。

时间复杂度 O(x)

空间复杂度 O(x)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e6 + 7;
bool vis[N];
vector<int> prime;
void get_prime(int n) {
for (int i = 2;i <= n;i++) {
if (!vis[i]) prime.push_back(i);
for (int j = 0;j < prime.size() && i * prime[j] <= n;j++) {
vis[i * prime[j]] = 1;
if (!(i % prime[j])) break;
}
}
}
bool solve() {
int x;
cin >> x;
if (x == 1) {
cout << 2 << '\n';
return true;
}
if (x == 3) {
cout << 4 << '\n';
return true;
}
if (x == 7) {
cout << 8 << '\n';
return true;
}
if (x & 1) {
for (int i = 0;i < prime.size() && 2 * prime[i] < x - 1;i++) {
if (!vis[x - 1 - prime[i]]) {
cout << 1LL * prime[i] * (x - 1 - prime[i]) << '\n';
return true;
}
}
}
else {
if (!vis[x - 1]) cout << 1LL * (x - 1) * (x - 1) << '\n';
else cout << 1LL * 2 * (x - 3) << '\n';
return true;
}
return false;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
get_prime(1e6);
while (t--) {
if (!solve()) cout << -1 << '\n';
}
return 0;
}

K

题解

知识点:贪心,数学。

给你前 n 种素数,每个素数有 ai 个。

size=i=1nai 。现在将这 size 个素数排成一个序列,设 f(i) 为序列中 [1,i] 的数的乘积的因子的数量。现在求 [1,size]f(i) 的和,即 i=1sizef(i) ,的最大值。

显然,我们需要尽可能让前面的 f(i) 的越大越好。我们知道,乘积的因子数量等于各个素数个数加 1 的乘积,通过一些尝试很容易发现均摊素数的个数,比连续安排同一种素数得到的结果要大很多,因此我们每次安排还能安排的素数中出现次数最小的那个素数。

我们设 cnti 表示出现至少 i 次的素数个数,我们发现 f(i) 的结果呈现 2,22,,2cnt1,2cnt113,,2cnt1cnt23cnt2, 。直接求和要加 size 次是不可行的,因此我们先用差分维护好 cnti ,随后用等比公式对每 cnti 个数直接求和。

precnti1 段的最后一个数字,那么 cnti 段的总和为 pre1i+1icnti1i+1i ,最后一个数字 pre=prei+1icnti ,于是就可以递推求和了。

时间复杂度 O(2105+n)

空间复杂度 O(2105)

代码

#include <bits/stdc++.h>
using ll = long long;
using namespace std;
const int P = 1e9 + 7;
ll qpow(ll a, ll k) {
ll ans = 1;
while (k) {
if (k & 1) ans = ans * a % P;
k >>= 1;
a = a * a % P;
}
return ans;
}
ll inv(ll a) { return qpow(a, P - 2); }
int cnt[200007];
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
for (int i = 1;i <= n;i++) {
int x;
cin >> x;
cnt[1]++;
cnt[x + 1]--;
}
for (int i = 1;i <= 2e5;i++) cnt[i] += cnt[i - 1];
int pre = 1;
int ans = 0;
for (int i = 1;i <= 2e5;i++) {
int f = 1LL * (i + 1) * inv(i) % P;
int g = qpow(f, cnt[i]);
ans = (ans + 1LL * pre * f % P * (1 - g + P) % P * inv(1 - f + P) % P) % P;
pre = 1LL * pre * g % P;
}
cout << ans << '\n';
return 0;
}
posted @   空白菌  阅读(174)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
点击右上角即可分享
微信分享提示