蔚来杯2022牛客暑期多校训练营6 ABGJM

比赛链接

A

题解

知识点:数学,构造。

题目要求构造一个长为 m 的序列 cm 自选,使得 c 的无限循环序列 b 中任意连续 ai 个数中都存在数 i 。因此,容易想到从起点 posi 开始每隔 ai 个数就填一次 i ,即在位置 (posi+kai)modm,0k<mai ,由此确定了构造的基本方法是填数。

但显然,直接挨个填数很可能会使得某个数的某个落点已经被其他数占据了。比如当 a=[5,4] 时,考虑构造 m=5 的序列:先填 1 得到 [1,0,0,0,0] ,再填 2 得到 [1,2,0,0,0] ,发现下一个位置是 c[0] ,已经有数字了。然而事实上,我们不容易在较小的时间复杂度内得到一个合适的起点,使得这个数的落点不会被其他数占据。因此,考虑调整 ai ,使得我们可以容易避免落点重合的情况。

证明1

显然,我们只能缩小 ai 使得,因为缩小后的 ai 满足时原来的 ai 一定满足,而放大不一定。

注意到,对于任意两个数 aiaj, 当 aj=dai,d1 ,若 k,m ,使得 posi+kai=posj+maj ,则有 posj=posi+kaimaj ,于是对于 m,都有 posj+maj=posi+kai+(mm)aj=posi+[k+(mm)d]ai ,即处处重合。

因此,当 aj=dai ,只要一处不重合,则所有落点都不重合。为了方便,将 ai 缩小成小于等于它的最大的 2 的幂 ai

但仅仅是这样只能保证两两不重合,还无法保证全都不重合。比如当 a=[8,8,8,8,4] ,考虑 m=8 ,按顺序填,填到 5 时会发现 [1,2,3,4,5,0,0,0] ,下一个位置是 c[0] ,还是被占据了。因此考虑改变填数顺序,使得后一个填的数不会被前面所有的数的位置影响。

证明2

从大到小的就是上述情况,不可行。

考虑从小到大,每次在第一个空位开始填。

由于每次都是从第一个空位填,所以不允许某个 ai 前面填了大于等于 ai 个位置,否则必然会循环回来落到开头被占据的位置,如上述情况。只要我们证明,对于任意一个 ai,其前方已经填的位置数 x<ai ,即可。

由于 ai>ai2 ,又条件告诉我们有 1ai12 ,那么现在有 1ai<2ai1

当填到 ai=2k 时,我们尝试构造 a1i12k ,使得从 0 开始连续占用最多的位置。注意到,如果 aj=2mai,m1,则使用 ai 等效于使用了 2maj ,且总和 2m1aj=1ai 也是等价的 ,所以我们按这个方法,无论怎么取,最优结果都是一样的。因此在 ai11a1++1ai1112k 下,为了方便,我们可以优先用小的 ai 构造,快速缩小可行范围。

显然,我们只能构造 a=[2,4,8,,2k] ,其倒数总和刚好是 112k ,而连续占用位置是 2k1 个。

于是我们证明了这个结论。

到这里其实最难的两个证明已经结束了。接下来我们思考一下 m 到底取多少合适。

证明3

长度 m 时占位为 mai ,因为每个 ai 对应的 i 至少出现一次,可得 mmax(a)

m=max(a) ,带入得 mai=maim ,因此 m=max(a) 是最小长度。

由于可能多出空位,但是可以随便填,所以全填 1

到此这道构造题就完全结束了。

时间复杂度 O(nlogn+max(a))

空间复杂度 O(n+max(a))

代码

#include <bits/stdc++.h>
using namespace std;
struct node {
int val;
int id;
}a[100007];
int c[1000007];
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++) {
cin >> a[i].val;
a[i].id = i;
int tmp = 1 << 30;
while (tmp > a[i].val) tmp >>= 1;
a[i].val = tmp;
}///使用2的幂,证明1
sort(a + 1, a + n + 1, [&](node a, node b) {
return a.val < b.val;
});///从小到大,证明2
int m = a[n].val;///c的长度,证明3
for (int i = 1, pos = 0;i <= n;i++) {
for (int j = pos;j < m;j += a[i].val) c[j] = a[i].id;///方法是填数
while (c[pos]) pos++;
}
cout << m << '\n';
for (int i = 0;i < m;i++) cout << max(1, c[i]) << ' ';///可能多出来空位随便填
cout << '\n';
return 0;
}

B

题解

知识点:枚举,前缀和,差分,树。

考虑树上前缀和与差分。维护当前递归的一条树链的深度到节点编号的映射数组 depL 即可差分,最后递归求前缀和。

时间复杂度 O(n)

空间复杂度 O(n)

代码

#include <bits/stdc++.h>
using namespace std;
vector<int> g[2000007];
int d[2000007];
int dep[2000007], depL[2000007];///节点深度,一条链上深度对应节点
int sum[2000007];///树上差分
void dfs(int u, int fa) {
dep[u] = dep[fa] + 1;
depL[dep[u]] = u;
sum[u]++;
sum[depL[max(0, dep[u] - d[u] - 1)]]--;
for (auto v : g[u]) {
if (v == fa) continue;
dfs(v, u);
}
}
void cal(int u, int fa) {
for (auto v : g[u]) {
if (v == fa) continue;
cal(v, u);
sum[u] += sum[v];
}
}
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 u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
for (int i = 1;i <= n;i++) cin >> d[i];
dfs(1, 0);
cal(1, 0);
for (int i = 1;i <= n;i++) cout << sum[i] << ' ';
cout << '\n';
return 0;
}

G

题解

知识点:模拟。

签到题。直接打表也行,模拟也行。

时间复杂度 O(n2)

空间复杂度 O(1)

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
for (int i = 1;i <= 13 * n + 19;i++) cout << '*';
cout << '\n';
for (int i = 1;i <= n;i++) {
cout << "*";
for (int j = 2;j <= 13 * n + 18;j++)
cout << ".";
cout << "*";
cout << '\n';
}
for (int i = 1;i <= 2 * n + 3;i++) {
cout << "*";
for (int j = 1;j <= n + 1;j++) cout << ".";
for (int j = 1;j <= 2 * n + 3;j++) {
if (i == 1 || i == 2 * n + 3) {
if (j == 1 || j == 2 * n + 3) cout << "@";
else cout << ".";
}
else {
if (j == 1 || j == 2 * n + 3 || j == i) cout << "@";
else cout << ".";
}
}
for (int j = 1;j <= n + 1;j++) cout << ".";
for (int j = 1;j <= 2 * n + 3;j++) {
if (i == 1 || i == n + 2) cout << "@";
else {
if (j == 1) cout << "@";
else cout << ".";
}
}
for (int j = 1;j <= n + 1;j++) cout << ".";
for (int j = 1;j <= 2 * n + 3;j++) {
if (i == 2 * n + 3) cout << "@";
else {
if (j == 1) cout << "@";
else cout << ".";
}
}
for (int j = 1;j <= n + 1;j++) cout << ".";
for (int j = 1;j <= 2 * n + 3;j++) {
if (i == 1 || i == n + 2 || i == 2 * n + 3) cout << "@";
else if (1 < i && i < n + 2) {
if (j == 1) cout << "@";
else cout << ".";
}
else if (n + 2 < i && i < 2 * n + 3) {
if (j == 2 * n + 3) cout << "@";
else cout << ".";
}
}
for (int j = 1;j <= n + 1;j++) cout << ".";
cout << "*";
cout << '\n';
}
for (int i = 1;i <= n;i++) {
cout << "*";
for (int j = 2;j <= 13 * n + 18;j++)
cout << ".";
cout << "*";
cout << '\n';
}
for (int i = 1;i <= 13 * n + 19;i++) cout << '*';
cout << '\n';
return 0;
}

J

题解

知识点:思维,数学。

A不能变,B只有A-B与B两个值,于是C的变换方法只有形如交替的A-B与B,而最终C的符号取决于偶数次变换还是奇数次。公式为:

{k(2BA)+C=x,Bk(2BA)C=x,

这里的 k 取负时的意义简单理解是交换了变换顺序,比如 [AB,B][B,A] 。只要找到整数 k 满足等式即可,注意 2BA=0 时需要特判。

时间复杂度 O(1)

空间复杂度 O(1)

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
bool solve() {
int a, b, c, x;
cin >> a >> b >> c >> x;
if (2 * b - a == 0) {
if (x - c == 0 || x + c - b == 0) cout << "Yes" << '\n';
else return false;
}
else if ((x - c) % (2 * b - a) == 0 || (x + c - b) % (a - 2 * b) == 0) cout << "Yes" << '\n';
else return false;
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

题解

知识点:线性dp,博弈论。

通过转移局面情况到起点,然后判断起点是否到达目标状态。

其中由于走法是固定向下或者向右,所以可以通过横纵坐标之和的奇偶性来决定当前是谁的回合。

对于A而言,两种走法有一种能得到目标状态就转移;对于B而言,两种走法有一种得不到目标状态就转移。

具体看代码注释qwq。

时间复杂度 O(nm)

空间复杂度 O(nm)

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
char dt[507][507];
int dp[507][507];
///三种局面是可以独立考虑的,分别看作能否一定走到A、B、(n,m)处状态
///用三位二进制分别表示(i, j)处A是否一定能走到A、B、(n, m)处状态
///1表示一定能走到,0表示不一定能走到(包括一定不)
///已知A处001,终点处为010,B处100
bool solve() {
int n, m;
cin >> n >> m;
for (int i = 1;i <= n;i++)
for (int j = 1;j <= m;j++)
cin >> dt[i][j];
for (int i = n;i >= 1;i--) {
for (int j = m;j >= 1;j--) {
if (dt[i][j] == 'A') { dp[i][j] = 1;continue; }
if (dt[i][j] == 'B') { dp[i][j] = 4;continue; }
if (i == n && j == m) { dp[i][j] = 2;continue; }
if (i == n) dp[i][j] = dp[i][j + 1];
else if (j == m) dp[i][j] = dp[i + 1][j];///无路可走的两种直接传递
else if ((i + j) & 1) dp[i][j] = dp[i + 1][j] & dp[i][j + 1];///可以通过坐标之和奇偶性得知是谁的回合,因为只能往下或者往右走,每轮都只会加一
else dp[i][j] = dp[i + 1][j] | dp[i][j + 1];///每种局面有0则B一定走0,有1则A一定走1
}
}
if (dp[1][1] & 1) cout << "yes" << ' ';
else cout << "no" << ' ';
if (dp[1][1] & 2) cout << "yes" << ' ';
else cout << "no" << ' ';
if (dp[1][1] & 4) cout << "yes" << ' ';
else cout << "no" << ' ';
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;
}
posted @   空白菌  阅读(67)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
点击右上角即可分享
微信分享提示