比赛链接

A

题解

知识点:数学。

算一下发现 \(3\) 最好,\(2,4\) 并列, \(4\) 以后递减。于是,特判 \(3\) ,其他取最小值。

(众所周知, \(e\) 进制最好qwq。

时间复杂度 \(O(1)\)

空间复杂度 \(O(1)\)

代码

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

B

题解

知识点:数论,构造。

注意到:

\[\begin{aligned}
c_i + c_{n-1-i} \equiv 2a_i \pmod m\\
c_i - c_{n-1-i} \equiv 2b_i \pmod m\\
\end{aligned}
\]

\(m\) 是个素数,显然当 \(m>2\) 时一定有解。因为 \(2\) 的逆元一定存在,故无论 \(c_i,c_{n-1-i}\) 计算结果为多少, \(a_i,b_i\) 一定可求。我们可以通过 \(c_i,c_{n-1-i}\) 计算结果,是偶数直接除以 \(2\) ,否则加减一次 \(m\) ,就可以得到 \(a_i,b_i\) 。

\(m = 2\) 时,就不一定有解,因为同余式右侧恒为 \(0\) ,要先满足相加减以后的余数为 \(0\) 否则无解。如果有解,那我们可以令 \(a_i = c_i,b_i = 0\) 。

时间复杂度 \(O(n)\)

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long; int c[100007];
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, m;
cin >> n >> m;
for (int i = 0;i < n;i++) cin >> c[i];
bool ok = 1;
if (m == 2) for (int i = 0;i < n;i++) ok &= !((c[i] + c[n - 1 - i]) & 1);
if (!ok) cout << "NO" << '\n';
else {
for (int i = 0;i < n;i++) {
int t = c[i] + c[n - i - 1];
cout << (t % 2 ? (t - m) / 2 : t / 2) << " \n"[i == n - 1];
}
for (int i = 0;i < n;i++) {
int t = c[i] - c[n - i - 1];
cout << (t % 2 ? (t + m) / 2 : t / 2) << " \n"[i == n - 1];
}
}
cout << "YES" << '\n';
return 0;
}

CD

题解

知识点:背包dp,枚举。

通常背包dp就是从 \(1\) 开始到 \(n\) 直接一个状态跑完,但这道题需要知道一定选和一定不选第 \(i\) 件物品带来的价值之差,从而得到使第 \(i\) 件物品一定选的价值严格大于一定不选的价值。

我们为了得到除了 \(i\) 以外的其他物品的选择情况,考虑设 \(f_{i,j},g_{i,j}\) 分别为考虑 \([1,i]\) / \([i,n]\) 的物品且总体积不超过 \(j\) 的最大价值。转移方程和普通背包dp一样就不写了。

对于第 \(i\) 个物品,我们求 \(a = \max_\limits{j \in [0,m]}(f_{i - 1,j} + g_{i + 1,m - j})\) 表示除去第 \(i\) 个物品的最大贡献,以及 \(b = \max_\limits{j \in [0,m-v_i]}(f_{i - 1,j} + g_{i + 1,m-v_i - j})+w_i\) 表示一定选第 \(i\) 个物品的最大贡献。随后,\(\max(0,a-b+1)\) 即让一定选的价值超过不选的价值,那么 \(i\) 就变成必选物品了。

(C题纯暴力枚举每个物品不选的情况都背包dp一遍,就不写了。

时间复杂度 \(O(nm)\)

空间复杂度 \(O(nm)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long; ll f[5007][5007], g[5007][5007];
ll v[5007], w[5007];
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, m;
cin >> n >> m;
for (int i = 1;i <= n;i++) cin >> v[i] >> w[i];
for (int i = 1;i <= n;i++) {
for (int j = 0;j <= m;j++) {
if (j < v[i]) f[i][j] = f[i - 1][j];
else f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
}
}
for (int i = n;i >= 1;i--) {
for (int j = 0;j <= m;j++) {
if (j < v[i]) g[i][j] = g[i + 1][j];
else g[i][j] = max(g[i + 1][j], g[i + 1][j - v[i]] + w[i]);
}
}
for (int i = 1;i <= n;i++) {
ll a = 0, b = 0;
for (int j = 0;j <= m;j++) a = max(a, f[i - 1][j] + g[i + 1][m - j]);
for (int j = 0;j <= m - v[i];j++) b = max(b, f[i - 1][j] + g[i + 1][m - v[i] - j] + w[i]); cout << max(0LL, a - b + 1) << '\n';
}
return 0;
}

E

题解

知识点:模拟。

直接模拟记录攻击次数 \(cnt\) ,分类讨论:

  1. \(a \geq h_i\) ,则一次能死的次数加一。
  2. 否则,若 \(a \leq tv_i\) 则无法击杀。否则,次数为 \(1+\left\lceil \dfrac{h-a}{a-tv_i} \right\rceil\) ,表示先攻击一次,后面每次攻击前都会恢复 \(tv_i\) ,所以下一次攻击有效伤害为 \(a- tv_i\) 。

最后时间为 \(1+t \cdot (cnt-1)\) 。

时间复杂度 \(O(n)\)

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long; int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
ll t, a;
cin >> n >> t >> a;
vector<pair<ll, ll>> mon;
for (int i = 1;i <= n;i++) {
ll h, v;
cin >> h >> v;
mon.push_back({ h,v });
}
ll cnt = 0;
for (auto [h, v] : mon) {
if (h <= a) cnt++;
else {
if (a <= t * v) {
cout << -1 << '\n';
return 0;
}
cnt += (h - a + a - t * v - 1) / (a - t * v) + 1;
}
}
cout << 1 + (cnt - 1) * t << '\n';
return 0;
}

F

题解

知识点:树,DFS,位运算。

先得到两个性质:

  1. 对于 \(fa\) ,其左右孩子满足 \(fa \mp lowbit(fa)/2\) ,因此如果 lowbit 处有连续两个 \(1\) 则是右孩子,否则为左孩子。
  2. 对于 \(x\) 为根的子树(除了 \(2^k\) ),节点数等于 \(2^{\text{x的高度}}-1 = 2 \cdot lowbit(x)-1\) 。

对于答案计数,可以考虑 \(x\) 爬到 \(2^k\) ,中间分类讨论走两侧的不同情况。

先序遍历:

  1. 先算上自己,答案加 \(1\) 。
  2. \(x\) 是 \(fa\) 的左孩子,那么答案加 \(1\) 。
  3. \(x\) 是 \(fa\) 的右孩子,那么答案加上左子树节点数再加一。

中序遍历:即 \(x\) 。

后序遍历:

  1. 先加上 \(x\) 为根的子树,答案加节点数。
  2. \(x\) 是 \(fa\) 的左孩子,答案不变。
  3. \(x\) 是 \(fa\) 的右孩子,那么答案加上左子树节点数。

时间复杂度 \(O(qk)\)

空间复杂度 \(O(1)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long; int k, q;
ll f(ll x) { return x & -x; }
ll fo(ll x) {
ll ans = 1;
while (x != (1LL << k)) {
ll fa;
if ((x >> 1) & f(x)) {
fa = x - f(x);
ans += 2 * f(fa - f(x));
}
else {
fa = x + f(x);
ans++;
}
x = fa;
}
return ans;
} ll lo(ll x) {
ll ans = x == (1LL << k) ? x : 2 * f(x) - 1;
while (x != (1LL << k)) {
ll fa;
if ((x >> 1) & f(x)) {
fa = x - f(x);
ans += 2 * f(fa - f(x)) - 1;
}
else fa = x + f(x);
x = fa;
}
return ans;
} int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> k >> q;
while (q--) {
ll x;
cin >> x;
cout << fo(x) << ' ' << x << ' ' << lo(x) << '\n';
}
return 0;
}

GH

题解

知识点:bfs,倍增,二分。

我们先求出 \(s(x_s,y_s)\) 到所有点的最短路 \(dis_{x,y}\),因为花费都是 \(1\) 可以直接bfs。

显然,我们可以模拟qcjj从 \(t(x_t,y_t)\) 出发开始走的路径,如果途中 \(dis_{x,y}\) 小于等于qcjj走到 \((x,y)\) 的距离,那这个点就是答案,这是EZ版本的解。

但HD版本的访问次数很多,不可能一次次模拟,考虑缩短每次模拟的次数。qcjj实际上没有必要一步一步走,假设我们知道第 \(k\) 步qcjj的坐标 \((x,y)\) ,如果 \(k \geq ans\) ,那一定有 \(dis_{x,y} \leq ans\) ,否则一定有 \(dis_{x,y} > ans\) ,因此答案是符合单调性的。

虽然可以二分答案了,但如何快速求出第 \(k\) 步的确切坐标就成了问题。我们考虑使用倍增的思想,求出 \(pos_{k,i,j}\) 表示 \((i,j)\) 为起点第 \(2^k\) 步的坐标,这样就能在线性复杂度预处理,对数复杂度求出确切坐标。

进一步思考,既然以及用倍增了,那再套一层二分就没必要了。因为两者都是折半查找,不过前者是从某一端逼近答案,后者是缩小区间锁定答案,那我们只用倍增逼近一次就结束了。

时间复杂度 \(O((nm + q)\log (nm))\)

空间复杂度 \(O(nm \log (nm))\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long; struct node {
int x, y;
};
int n, m;
char dt[807][807]; const int dir[4][2] = { {0,-1},{0,1},{-1,0},{1,0} };
int dis[807][807];
queue<node> q;
void bfs(node s) {
for (int i = 0;i < n;i++)
for (int j = 0;j < m;j++)
dis[i][j] = -1;
dis[s.x][s.y] = 0;
q.push(s);
while (!q.empty()) {
auto [x, y] = q.front();
q.pop();
for (int i = 0;i < 4;i++) {
int xx = x + dir[i][0];
int yy = y + dir[i][1];
if (~dis[xx][yy] || dt[xx][yy] == '#') continue;
dis[xx][yy] = dis[x][y] + 1;
q.push({ xx,yy });
}
}
} map<char, int> mp = { {'L',0},{'R',1},{'U',2},{'D',3} };
node pos[20][807][807];
void pos_init() {
for (int i = 0;i < n;i++) {
for (int j = 0;j < m;j++) {
if (dt[i][j] == '#') pos[0][i][j] = { -1,-1 };
if (dt[i][j] == '.') pos[0][i][j] = { i,j };
else {
int xx = i + dir[mp[dt[i][j]]][0];
int yy = j + dir[mp[dt[i][j]]][1];
if (xx < 0 || xx >= n || yy < 0 || yy >= m || dt[xx][yy] == '#') pos[0][i][j] = { i,j };
else pos[0][i][j] = { xx,yy };
}
}
}
for (int k = 1;k < 20;k++) {
for (int i = 0;i < n;i++) {
for (int j = 0;j < m;j++) {
auto [x, y] = pos[k - 1][i][j];
pos[k][i][j] = pos[k - 1][x][y];
}
}
}
} int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> n >> m;
int sx, sy;
cin >> sx >> sy;
node s = { sx,sy };
int q;
cin >> q;
for (int i = 0;i < n;i++)
for (int j = 0;j < m;j++)
cin >> dt[i][j]; bfs(s);
pos_init();
while (q--) {
int xt, yt;
cin >> xt >> yt;
node t = { xt,yt };
int ans = 0;
for (int i = 19;i >= 0;i--) {
auto [x, y] = pos[i][t.x][t.y];
if (!~dis[x][y] || dis[x][y] > ans + (1 << i)) {
t = { x,y };
ans += 1 << i;
}//不能等于,ans的意义是不可行的最大值;加了等于意义就乱了
}
cout << (++ans == 1 << 20 ? -1 : ans) << '\n';
}
return 0;
}

J

题解

方法一

知识点:拓扑排序,枚举。

因为保证一定符合某一种解,那关系图一定是DAG。

我们知道拓扑排序能在不破坏DAG节点顺序下,求出节点的相对顺序。因此我们可以使用拓扑排序,同时记录节点位置 \(pos\) 。通过 \(pos\) ,我们可以得到一个节点的位置上下确界 \([maxl,minr]\) ,显然如果节点 \(i\) 是确定的那么这个区间只会包括 \(i\) ,如果不是,那么被这个区间包括的所有点都是不能确定的。因此,我们可以对所有点枚举边界即可排除不确定的点。

时间复杂度 \(O(n+m)\)

空间复杂度 \(O(n+m)\)

方法二

知识点:dfs,STL,枚举。

考虑求一个传递闭包,从而可以直接得到一个点与所有点的关系。因为图是DAG,我们可以通过dfs遍历图获得,不需要floyd。

最后,对每个点记录小于等于自己和大于等于自己的个数和,如果等于 \(n+1\) 那说明这个点和其他 \(n-1\) 个点的关系完全确定,就能知道位置了。

时间复杂度 \(O\left(\dfrac{nm}{w} \right)\)

空间复杂度 \(O(n+m)\)

代码

方法一

#include <bits/stdc++.h>
using namespace std;
using ll = long long; int n, m;
vector<int> g[1007]; int deg[1007];
queue<int> q;
int ans[1007];
int pos[1007], cnt;
void toposort() {
for (int i = 1;i <= n;i++) if (!deg[i]) q.push(i);
while (!q.empty()) {
int u = q.front();
q.pop();
cnt++;
ans[cnt] = u;
pos[u] = cnt;
for (auto v : g[u]) {
deg[v]--;
if (!deg[v]) q.push(v);
}
}
} int maxl[1007], minr[1007];
int b[1007]; int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1;i <= m;i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
deg[v]++;
}
toposort();
for (int i = 1;i <= n;i++) minr[i] = n + 1;
for (int i = 1;i <= n;i++) {
for (auto v : g[i]) {
minr[i] = min(minr[i], pos[v]);
maxl[v] = max(maxl[v], pos[i]);
}
} for (int i = 1;i <= n;i++) b[i] = ans[i];
int r = 1;
for (int i = 1;i <= n;i++) {
if (r > i) b[i] = -1;
r = max(r, minr[ans[i]]);
}
int l = n;
for (int i = n;i >= 1;i--) {
if (l < i) b[i] = -1;
l = min(l, maxl[ans[i]]);
} for (int i = 1;i <= n;i++)cout << b[i] << " \n"[i == n];
return 0;
}

方法二

#include <bits/stdc++.h>
using namespace std;
using ll = long long; int n, m;
vector<int> g[1007]; bool vis[1007];
bitset<1007> bs[1007];
void dfs(int u) {
if (vis[u]) return;
vis[u] = 1;
for (auto v : g[u]) {
dfs(v);
bs[u] |= bs[v];
}
} int b[1007]; int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1;i <= n;i++) bs[i][i] = 1, b[i] = -1;
for (int i = 1;i <= m;i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
}
for (int i = 1;i <= n;i++) if (!vis[i]) dfs(i);
for (int i = 1;i <= n;i++) {
int cnt = 0;
for (int j = 1;j <= n;j++) cnt += bs[j][i];
if (bs[i].count() + cnt == n + 1) b[cnt] = i;
}
for (int i = 1;i <= n;i++)cout << b[i] << " \n"[i == n];
return 0;
}

L

题解

知识点:数学。

可以先求出 \(l_b\) ,再求出 \(l_a,l_c\) ,随后复制一份排序判断,因为输出要按原来顺序。

时间复杂度 \(O(1)\)

空间复杂度 \(O(1)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long; bool solve() {
int A, B, C;
cin >> A >> B >> C;
if ((A - B + C) & 1) return false;
vector<int> v(4);
v[2] = (A - B + C) / 2;
v[3] = A - v[2];
v[1] = C - v[2];
auto g = v;
sort(g.begin() + 1, g.end());
if (g[1] <= 0 || g[1] + g[2] <= g[3]) return false;
cout << "YES" << '\n';
cout << v[1] << ' ' << v[2] << ' ' << v[3] << '\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 << "NO" << '\n';
}
return 0;
}

M

题解

知识点:构造。

众所周知, \(1,2,3\) 是构造不出三角形的,循环打就行。

时间复杂度 \(O(n)\)

空间复杂度 \(O(1)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long; int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
for (int i = 0;i < n;i++) cout << "123"[i % 3] << ' ';
cout << '\n';
return 0;
}

2023牛客寒假算法基础集训营4 A-H+JLM的更多相关文章

  1. 2020牛客寒假算法基础集训营2 J题可以回顾回顾

    2020牛客寒假算法基础集训营2 A.做游戏 这是个签到题. #include <cstdio> #include <cstdlib> #include <cstring ...

  2. 2020牛客寒假算法基础集训营1 J题可以回顾回顾

    2020牛客寒假算法基础集训营1 这套题整体来说还是很简单的. A.honoka和格点三角形 这个题目不是很难,不过要考虑周全,面积是1,那么底边的长度可以是1也可以是2, 注意底边1和2会有重复的, ...

  3. Applese 的毒气炸弹 G 牛客寒假算法基础集训营4(图论+最小生成树)

    链接:https://ac.nowcoder.com/acm/contest/330/G来源:牛客网 Applese 的毒气炸弹 时间限制:C/C++ 2秒,其他语言4秒 空间限制:C/C++ 262 ...

  4. 牛客寒假算法基础集训营3B 处女座的比赛资格(用拓扑排序解决DAG中的最短路)

    链接:https://ac.nowcoder.com/acm/contest/329/B 来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 262144K,其他语言5242 ...

  5. 牛客寒假算法基础集训营4 I题 Applese 的回文串

    链接:https://ac.nowcoder.com/acm/contest/330/I 来源:牛客网 自从 Applese 学会了字符串之后,精通各种字符串算法,比如--判断一个字符串是不是回文串. ...

  6. 牛客寒假算法基础集训营4 I Applese 的回文串

    链接:https://ac.nowcoder.com/acm/contest/330/I来源:牛客网 自从 Applese 学会了字符串之后,精通各种字符串算法,比如……判断一个字符串是不是回文串. ...

  7. 牛客寒假算法基础集训营2 【处女座与复读机】DP最小编辑距离【模板题】

    链接:https://ac.nowcoder.com/acm/contest/327/G来源:牛客网 一天,处女座在牛客算法群里发了一句“我好强啊”,引起无数的复读,可是处女座发现复读之后变成了“处女 ...

  8. 欧拉函数-gcd-快速幂(牛客寒假算法基础集训营1-D-小a与黄金街道)

    题目描述: 链接:https://ac.nowcoder.com/acm/contest/317/D来源:牛客网小a和小b来到了一条布满了黄金的街道上.它们想要带几块黄金回去,然而这里的城管担心他们拿 ...

  9. 牛客寒假算法基础集训营3处女座和小姐姐(三) (数位dp)

    链接:https://ac.nowcoder.com/acm/contest/329/G来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 262144K,其他语言52428 ...

  10. 牛客寒假算法基础集训营4 F Applese 的大奖

    链接:https://ac.nowcoder.com/acm/contest/330/H来源:牛客网 Applese 和它的小伙伴参加了一个促销的抽奖活动,活动的规则如下:有一个随机数生成器,能等概率 ...

随机推荐

  1. 我的Vue之旅 09 数据数据库表的存储与获取实现 Mysql + Golang

    第四期 · 将部分数据存储至Mysql,使用axios通过golang搭建的http服务器获取数据. 新建数据库 DROP DATABASE VUE; create database if not e ...

  2. Linux网络通信(TCP套接字编写,多进程多线程版本)

    预备知识 源IP地址和目的IP地址 IP地址在上一篇博客中也介绍过,它是用来标识网络中不同主机的地址.两台主机进行通信时,发送方需要知道自己往哪一台主机发送,这就需要知道接受方主机的的IP地址,也就是 ...

  3. C++初阶(命名空间+缺省参数+const总结+引用总结+内联函数+auto关键字)

    命名空间 概述 在C/C++中,变量.函数和后面要学到的类都是大量存在的,这些变量.函数和类的名称将都存在于全局作用域中,可能会导致很多冲突.使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲 ...

  4. 图扑 Web SCADA 零代码组态水泥生产工艺流程 HMI

    前言 水泥是建筑工业三大基本材料之一,素有"建筑工业的粮食"之称.2022 年 1-9 月水泥产量为 15.63 亿吨,生产方法包括新型干法.立窑.湿窑.干法中空窑和立波尔窑等. ...

  5. 关于led蓝牙控制器ble通信分析

    前言 前几天在网上买了一个led蓝牙控制器,可以用手机app通过蓝牙连接控制rgb led灯,当然这个也是属于ble通信.之前我写过一篇体重称蓝牙通信的,不过那个较为简单,数据也是靠分析出来的. 这次 ...

  6. Django基础笔记6(Django中间件)

    Django自带的中间件 中间件执行流程 自定义中间件 Middle.py class Middle1(MiddlewareMixin): def process_request(self, requ ...

  7. bug处理记录:java.util.UnknownFormatConversionException: Conversion = 'Y'

    1. 报错: java.util.UnknownFormatConversionException: Conversion = 'Y' at java.util.Formatter$FormatSpe ...

  8. Linux通过脚本实现多台主机统一部署

    该脚本分成三部分,一部分是获取信息的脚本:getInfo.sh 一个是main脚本:main.sh.一个是ssh连接主机脚本:sshing.sh main.sh #是否检查主机是否存活host_che ...

  9. 详解redis网络IO模型

    前言 "redis是单线程的" 这句话我们耳熟能详.但它有一定的前提,redis整个服务不可能只用到一个线程完成所有工作,它还有持久化.key过期删除.集群管理等其它模块,redi ...

  10. c++11 线程池--鸿蒙OS

    一个你应该学习的线程池 说明 原理:线程+优先级队列.源码没有涉及STL,没有复杂高深的语法,安全性做得很好: queue的安全使用方式top和pop以及empty的判断都是使用了 std::lock ...