Codeforces Round #847 (Div. 3) A-G

比赛链接

A

题意

判断输入字符串与 π 的最长前缀匹配,不超过 30 位。

题解

知识点:模拟。

抄样例最后一个 30 都正确的,直接匹配。

时间复杂度 O(1)

空间复杂度 O(1)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
bool solve() {
string s;
cin >> s;
string pi = "314159265358979323846264338327";
int cnt = 0;
for (int i = 0;i < s.size();i++) {
if (s[i] != pi[i]) break;
cnt++;
}
cout << cnt << '\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;
}

B

题意

n 个骰子,数字 ai[1,6] 的面朝上,现在给你骰子面朝上数字的总和 s ,以及去掉一个最大值的数字总和 r ,要求还原一个合法的 ai

题解

知识点:枚举。

方法有很多,这里提供一种写起来很方便的。

先存一个最大值 mx=sr ,依次给每个骰子分配点数 min(mx,r) ,超过最大值直接给最大值。

但是为了保证不出现数字为 0 的情况,一开始先给所有骰子分配 1 点。

时间复杂度 O(n)

空间复杂度 O(1)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
bool solve() {
int n, s, r;
cin >> n >> s >> r;
s -= n;
r -= n - 1;
int mx = s - r;
cout << mx + 1 << ' ';
for (int i = 2;i <= n;i++) {
cout << min(r, mx) + 1 << ' ';
r -= min(r, mx);
}
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;
}

C

题意

对一个排列 p ,生成 n 个长为 n1 新序列,第 i 个序列由 p 除了 pi 的元素构成。

现在给你 p 生成的 n 个序列,但顺序是打乱的,要求还原一个合法的 p

题解

知识点:构造。

注意到 p1n 个序列的第一位会出现 n1>1 次,而 p2 只会出现 1 次 ,我们可以直接确定 p1 。确定了 p1 ,我们找到没有 p1 的一个序列,里面包含了 p2,,pn 直接输出即可。

时间复杂度 O(n2)

空间复杂度 O(n2)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int a[107][107];
int cnt[107];
bool solve() {
int n;
cin >> n;
int fst = 0;
vector<int> cnt(n + 1);
for (int i = 1;i <= n;i++) {
for (int j = 1;j <= n - 1;j++) {
cin >> a[i][j];
}
cnt[a[i][1]]++;
if (cnt[a[i][1]] > 1) fst = a[i][1];
}
cout << fst << ' ';
for (int i = 1;i <= n;i++) {
if (a[i][1] != fst) {
for (int j = 1;j <= n - 1;j++) cout << a[i][j] << " \n"[j == n - 1];
break;
}
}
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

题意

给定一组数 ai ,要求分出最少的分组,每组要求由连续上升的正整数构成。

对于连续上升,如 3,4,5,6 是连续上升的,而 1,3,4,5 不是,因为 1,3 中间跳过了 2

题解

知识点:贪心,枚举。

map 记录每个数的个数,从小到大遍历,设当前数字为 x ,上一次的数字为 precnt 表示数字的个数:

  1. 如果 x>pre+1 ,我们就必须开新的 cntx 个分组,答案加 cntx
  2. 如果 x=pre+1 ,则可以与上一次共用分组 cntx 个分组,但如果 cntx>cntpre ,那么多出来的部分要开新的分组,答案加 cntxcntpre

时间复杂度 O(nlogn)

空间复杂度 O(n)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
bool solve() {
int n;
cin >> n;
map<int, int> mp;
for (int i = 1;i <= n;i++) {
int x;
cin >> x;
mp[x]++;
}
int ans = 0, pre = -1;
for (auto [x, y] : mp) {
if (x > pre + 1) ans += y;
else ans += max(0, y - mp[pre]);
pre = x;
}
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;
}

E

题意

给定一个正整数 x229 ,找到两个正整数 a,b232 ,满足 a+b2=ab=x

题解

知识点:位运算,构造。

我们分析 x 的第 ixi=1 ,则 a,b 相关位置的情况,假设 a,b 一开始都为 0

  1. 首先满足 ab=x ,显然 ai,bi 必须是一个 1 和一个 0 ,我们不妨设 ai=1,bi=0

  2. 其次要满足 a+b2=x ,根据1得到的 ai+bi=1 不能满足除以 2 使得 xi=1 ,考虑对附近其他位做调整。

    考虑调整 i+1 位,那么 ai+1,bi+1 必须是一个 1 和一个 0 ,我们不妨设 ai+1=1,bi+1=0 。但是,这样 xi+1 必须等于 1 ,假设 xi+1=1,那么对于 i+1 位的第二步因为第 i 位锁定了,只能再往 i+2 位考虑,这是没有尽头的,所以这条路走不通。

    考虑调整 i1 位,那么就必须 ai1=bi1=1 才能产生一个进位使得 xi=1 。此时, xi1 必须等于 0 ,假设 xi1=0 就恰好满足所有需求。

综上,对于任何一个 xi=1 要满足 xi1=0 时才有解(特别地, x1=1 时无解)。满足有解条件后,我们考虑令 ai=1,bi=0ai1=bi1=1 ,即 a=3x2,b=x2

时间复杂度 O(1)

空间复杂度 O(1)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
bool solve() {
int x;
cin >> x;
int y = x >> 1;
if ((x & 1) || (x & y)) return false;
else cout << (x | y) << ' ' << y << '\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;
}

F

题意

题解

方法一

知识点:根号分治,bfs,贪心。

做法很简单,每加入一个黑点就bfs一遍附近找到最近的距离,主要在两个优化上:

  1. fu 记录距离 u 最近的黑点的距离,每次只更新 fv>fu+1 的情况,能有效减小bfs的范围。
  2. ans 答案来限制搜索深度,因为距离大于 ans 的点没必要更新了,下一次更新 ans 一定在 fuans1 的情况。

证明:

在前 n 次,一定能使得 ans 小于等于 2n ,期间最多遍历 nn 次。

在这之后,优化1能保证每个点只会在 f 变小时被遍历,优化2保证更新的距离不超过 ans2n 。综上,每个点的 f 最多只会被更新 2n 次,即每个点遍历不会超过 2n 次。

综上复杂度是 O(nn)

时间复杂度 O(nn)

空间复杂度 O(n)

方法二

知识点:根号分治,dfs,贪心。

不同于方法一的bfs遍历,这种方法设 fu 为以 u 为根的子树中到 u 最近的黑点的距离,这样每次只需要更新 u 到根节点 1 路径上的点即可,并且保证更新层数不超过当前的 ans ,就可以做到和方法一一样的复杂度,但实际上跑的更快。

证明同方法一类似,前 n 次能让 ans2n ,后续每次遍历不超过 ans2n 个点。

时间复杂度 O(nn)

空间复杂度 O(n)

代码

方法一

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
struct Graph {
struct edge {
int v, nxt;
};
int idx;
vector<int> h;
vector<edge> e;
Graph(int n, int m):idx(0), h(n + 1), e(m + 1) {}
void init(int n) {
idx = 0;
h.assign(n + 1, 0);
}
void add(int u, int v) {
e[++idx] = { v,h[u] };
h[u] = idx;
}
};
const int N = 200007, M = N << 1;
int c[N];
Graph g(N, M);
int f[N];
bool solve() {
int n;
cin >> n >> c[1];
for (int i = 2;i <= n;i++) cin >> c[i];
g.init(n);
for (int i = 1;i < n;i++) {
int u, v;
cin >> u >> v;
g.add(u, v);
g.add(v, u);
}
for (int i = 1;i <= n;i++) f[i] = n + 1;
int ans = n + 1;
for (int i = 1;i <= n;i++) {
ans = min(ans, f[c[i]]);
f[c[i]] = 0;
queue<int> q;
q.push(c[i]);
while (!q.empty()) {
int u = q.front();
q.pop();
if (f[u] >= ans - 1) continue;
for (int j = g.h[u];j;j = g.e[j].nxt) {
int v = g.e[j].v;
if (f[v] > f[u] + 1) {
f[v] = f[u] + 1;
q.push(v);
}
}
}
if (i > 1) cout << ans << " \n"[i == 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;
}

方法二

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
struct Graph {
struct edge {
int v, nxt;
};
int idx;
vector<int> h;
vector<edge> e;
Graph(int n, int m):idx(0), h(n + 1), e(m + 1) {}
void init(int n) {
idx = 0;
h.assign(n + 1, 0);
}
void add(int u, int v) {
e[++idx] = { v,h[u] };
h[u] = idx;
}
};
const int N = 200007, M = N << 1;
int c[N];
Graph g(N, M);
int fa[N];
int f[N];
void dfs(int u, int fa) {
::fa[u] = fa;
for (int i = g.h[u];i;i = g.e[i].nxt) {
int v = g.e[i].v;
if (v == fa) continue;
dfs(v, u);
}
}
bool solve() {
int n;
cin >> n >> c[1];
for (int i = 2;i <= n;i++) cin >> c[i];
g.init(n);
for (int i = 1;i < n;i++) {
int u, v;
cin >> u >> v;
g.add(u, v);
g.add(v, u);
}
dfs(1, 0);
for (int i = 1;i <= n;i++) f[i] = n + 1;
int ans = n + 1;
for (int i = 1;i <= n;i++) {
int u = c[i], dis = 0;
while (u && dis < ans) {
ans = min(ans, dis + f[u]);
f[u] = min(f[u], dis);
dis++;
u = fa[u];
}
if (i > 1)cout << ans << " \n"[i == 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;
}

G

题意

给一张 n 个点 m 条边的连通无向图,在图上的 b 个点会存在 bonus 标记(不可移动)。游戏开始前,会存在 ptoken 在图点上,可以按照以下步骤移动:

  1. 开局可以随意移动一个。
  2. 若某次 token 移动到了 bonus 点,那么可以有一次移动其他 token 机会。
  3. token 可以重叠。
  4. 某次移动使得 token 到编号为 1 的点,则游戏立刻胜利。

问游戏是否会胜利。

题解

知识点:模拟,贪心,枚举。

我们从两方面考虑:

  1. 能使 token 到达 1 的路径。
  2. token 能提供多少次移动机会。

先考虑何种路径才能到达 1 。为了保证每次移动都有下次机会,那么到 1 的路径除了起点和终点 1 都必须是 bonus 点。同时,我们还需要知道移动几次才能到达 1 ,方便后面比较其他点的贡献。因此,我们从 1 bfs,遇到不是 bonus 的点只更新距离,不放队列扩展。

再考虑 token 能提供多少机会。在此之前,我们对 bonus 点的性质进行讨论,发现如果 bonus 点相邻另一个 bonus 点,那么进入这个 bonus 点就能提供无限次机会,而剩下一些孤儿 bonus 点,则进入这个点只会提供一次机会,因此我们先对 bonus 点记录是否无限的状态,这个可以在建图的时候就处理好。之后,我们遍历所有 token ,如果他们的邻居是无限 bonus 点则可以提供无限次机会,这里可以记为 n 次;如果他们的邻居是孤儿 bonus 点则只能提供一次,记为 1 次。

最后我们求出 token 贡献总和,枚举每个 token 点判断能否到达 1 。如果可达,则减去这个点的贡献求出其他点的总贡献,如果其他点的贡献大于等于距离减 1 (开局第一次移动不需要任何 bonus )则一定可以到达。

时间复杂度 O(n+m)

空间复杂度 O(n+m)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 200007;
vector<int> g[N];
int st[N], dis[N], val[N];
bool solve() {
int n, m;
cin >> n >> m;
int p, b;
cin >> p >> b;
for (int u = 1;u <= n;u++) st[u] = 0, g[u].clear(), dis[u] = -1, val[u] = 0;
for (int i = 1, x;i <= p;i++) cin >> x, st[x] |= 1;
for (int i = 1, x;i <= b;i++) cin >> x, st[x] |= 2;
for (int i = 1;i <= m;i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
if ((st[u] & 2) && (st[v]) & 2) {
st[u] |= 4;
st[v] |= 4;
}
}
dis[1] = 0;
queue<int> q;
q.push(1);
while (!q.empty()) {
int u = q.front();
q.pop();
for (auto v : g[u]) {
if (~dis[v]) continue;
dis[v] = dis[u] + 1;
if (st[v] & 2) q.push(v);
}
}
for (int u = 1;u <= n;u++) if (st[u] & 1) for (auto v : g[u]) if (st[v] & 2) { val[u] = st[v] & 4 ? n : 1;if (st[v] & 4) break; }
ll sum = 0;
for (int u = 1;u <= n;u++) if (st[u] & 1) sum += val[u];
for (int u = 1;u <= n;u++) if (st[u] & 1) if (~dis[u] && sum - val[u] >= dis[u] - 1) { cout << "YES" << '\n'; return true; }
return false;
}
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;
}
posted @   空白菌  阅读(174)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
点击右上角即可分享
微信分享提示