2025牛客暑期多校训练营3
A. Ad-hoc Newbie
题意:给定一个\(f\)数组,使得\(f[i] \leq i\)。你要构造一个矩阵\(a\),使得\(mex(a[1][i], a[2][i], ... a[n][i]) = mex(a[i][1], .. a[i][n]) = f[i]\)。
可以构造\(ans[i][i] = 1\),然后\(ans[i][j] = ans[j][i] = i + 1\)。一个这样的矩阵。
然后,把所有\(ans[i][j] = f[i]\)或\(ans[i][j] = f[j]\)的位置变成\(0\)。
因为\(f[i] \leq i\),除了\(ans[i][i]\)是\(1\)外,\(ans[i][j]\)肯定大于\(f[i]\)和\(f[j]\)。而\(ans[i][i]\)肯定只会因为\(f[i]\)而变成\(0\),不会影响其它列,而这些大于\(f[i]\)的位置变成\(0\)也不会影响这一行列的\(mex\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::vector ans(n, std::vector<int>(n));
for (int i = 0; i < n; ++ i) {
ans[i][i] = 1;
}
for (int i = 0; i < n; ++ i) {
for (int j = i + 1; j < n; ++ j) {
ans[i][j] = ans[j][i] = i + 2;
}
}
for (int i = 0; i < n; ++ i) {
for (int j = 0; j < n; ++ j) {
if (ans[i][j] == a[i] || ans[i][j] == a[j]) {
ans[i][j] = 0;
}
}
}
for (int i = 0; i < n; ++ i) {
for (int j = 0; j < n; ++ j) {
std::cout << ans[i][j] << " \n"[j == n - 1];
}
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
B. Bitwise Puzzle
题意:三个数\(a, b, c\),有四种操作,求\(64\)次操作内把\(a, b\)都变成\(c\)。
如果\(b\)的最高位小于\(a\)的最高位,则\(b=b\oplus a\)。
然后设\(b\)最高位为\(p\),那么我们可以利用这一位,把\(a\)的高\(30 - p\)位变得和\(c\)相同。也就是拿\(a\)的第\(p\)位和依次\(c\)的最高位比较,看要不要异或\(b\),然后每次把\(a\)左移。
此时再把低\(p\)位变的和\(c\)一样,只需要\(b\)不断除二,把最高位一步步拉低,就可以实现。最后\(b\)除成了\(0\),异或上\(a\)就变的和\(a\)一样了,此时\(a\)和\(c\)一样,就构造了一组解。
\(a, b\)的乘\(2\)除\(2\)操作总共\(31\)次,异或操作总共\(31\)次,一开始\(b\)和\(a\)异或一次,最后又和\(a\)异或一次,总共是\(31 +31 + 1 + 1= 64\)次。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
i64 a, b, c;
std::cin >> a >> b >> c;
if (a == 0 && b == 0) {
if (c != 0) {
std::cout << -1 << "\n";
return;
}
std::cout << 0 << "\n";
std::cout << "\n";
return;
}
std::vector<int> ans;
if (std::__lg(b) < std::__lg(a)) {
ans.push_back(4);
b ^= a;
}
int p = 30;
for (int i = 30; i >= 0; -- i) {
if (b >> i & 1) {
p = i;
break;
}
}
int j = 30;
for (int i = 0; i < 30 - p; ++ i, -- j) {
if ((a >> p & 1) != (c >> j & 1)) {
ans.push_back(3);
a ^= b;
}
ans.push_back(1);
a <<= 1;
}
for (int i = p; i >= 0; -- i, -- j) {
if ((a >> i & 1) != (c >> j & 1)) {
ans.push_back(3);
a ^= b;
}
ans.push_back(2);
b >>= 1;
}
ans.push_back(4);
b ^= a;
std::cout << ans.size() << "\n";
for (auto & x : ans) {
std::cout << x << " " ;
}
std::cout << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
D. Distant Control
题意:给你一个长度为\(n\)的\(01\)数组和一个\(a\),每次可以把连续\(a\)个\(1\)变成\(0\),或者把连续\(a+1\)个\(0\)变成\(1\)。求最多有多少个\(1\)。
如果我们可以进行第一个操作,肯定是操作一段旁边是\(0\)的,就得到大于等于\(a+1\)的连续个\(0\),就可以进行\(2\)操作,\(1\)的个数多\(1\),如此重复就可以全部变成\(1\)。同理如果可以进行\(2\)操作,那么我们可以进行\(1\)操作,就可以全部变成\(1\)。于是只要有连续\(a\)个\(1\)或者连续\(a+1\)个\(0\)就一定是\(n\),否则就是\(1\)的个数。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, m;
std::cin >> n >> m;
std::string s;
std::cin >> s;
for (int i = 0; i < n; ++ i) {
int j = i;
while (j + 1 < n && s[j + 1] == s[i]) {
++ j;
}
if (j - i + 1 >= m + (s[i] == '0')) {
std::cout << n << "\n";
return;
}
i = j;
}
std::cout << std::ranges::count(s.begin(), s.end(), '1') << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
E. Equal
题意:给你一个数组,每次选两个数除掉它们的一个共同因子,或者让他们同时乘上一个数。求能不能把所有数变成一样的。
注意到三个数\(x, y, z\),可以先\(x=xz, y=yz\),然后\(xz=xyz, z=yz\),再\(yz=xyz, yz=xyz\)。这样就变成相同的了,然后每次可以拿出其中一个数和另外两个数操作,得到\(xyz, xyz, xyzde, xyzde, xyzde\),此时剩下的两个\(xyz\)都乘上\(de\)就可以满足条件。然后归纳可得,奇数个一定可以变成一样的。
再考虑偶数个,考虑把所有质因子都拿出来一对一对除掉,那么对于每个质数,问题变成有\(n\)个数,每个数有\(a_i\)个\(p\),每次选两个不同位置减一。能不能把数组都变成\(0\),这就是经典问题了,记\(sum\)为\(p\)出现的总次数,\(max\)为\(p\)在一个数上出现的最大次数,那么如果\(max > \lfloor \frac{sum}{2} \rfloor\)或\(sum\)是奇数则不能满足,否则一定能把\(p\)消除干净。这样我们得到了一堆\(1\),和有若干个质因子的数,且这些质因子其它数是没有的。然后发现三个\(1\)可以拼出来\(p^{2k}\),那么如果剩下的质因子个数不是偶数个,无解。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
std::vector<int> minp, primes;
void sieve(int n) {
minp.assign(n + 1, 0);
primes.clear();
for (int i = 2; i <= n; i++) {
if (minp[i] == 0) {
minp[i] = i;
primes.push_back(i);
}
for (auto p : primes) {
if (i * p > n) {
break;
}
minp[i * p] = p;
if (p == minp[i]) {
break;
}
}
}
}
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 << "YES\n";
return;
}
std::map<int, int> sum, max;
for (int i = 0; i < n; ++ i) {
int x = a[i];
while (x > 1) {
int p = minp[x];
int cnt = 0;
while (x % p == 0) {
x /= p;
++ cnt;
}
sum[p] += cnt;
max[p] = std::max(max[p], cnt);
}
}
bool has = false;
for (auto & [p, s] : sum) {
if (max[p] > sum[p] / 2) {
if ((max[p] - (sum[p] - max[p])) % 2) {
std::cout << "NO\n";
return;
}
has = true;
} else {
if (sum[p] % 2) {
std::cout << "NO\n";
return;
}
}
}
if (!has || n > 2) {
std::cout << "YES\n";
} else {
std::cout << "NO\n";
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
sieve(5e6);
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
F. Flower
题意:每次拿\(a\)个再拿\(b\)个,求使得第\(n\)个在\(a\)个里最少删几个数。
先求得\(n\)本来是在\(a\)个里还是\(b\)个,直接对\(a+b\)取模就行。
如果\(n\leq a\),则需要删前面\(n\)个数,否则不用删。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, a, b;
std::cin >> n >> a >> b;
if (n <= a) {
std::cout << "Sayonara\n";
return;
}
n %= (a + b);
if (n == 0) {
n = a + b;
}
if (n > a) {
std::cout << 0 << "\n";
} else {
std::cout << n << "\n";
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
H. Head out to the Target
暴力模拟发现这个可以到达的点一定是根所在的一个联通块。那么对于每个\((u, l, r)\),找可以得到的最近祖先,然后暴力模拟。
具体可以用倍增求的祖先,然后计算和祖先的距离,如果小于等于\(r-l\)直接就可以到达,否则跳到祖先最深可以到的点,一步一步往上爬模拟就行。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<std::vector<int>> adj(n);
for (int i = 1; i < n; ++ i) {
int j;
std::cin >> j;
-- j;
adj[j].push_back(i);
}
std::vector<std::tuple<int, int, int>> a(k);
for (int i = 0; i < k; ++ i) {
int u, l, r;
std::cin >> u >> l >> r;
-- u;
a[i] = {u, l, r};
}
int lg = std::__lg(n) + 1;
std::vector fa(n, std::vector<int>(lg + 1));
std::vector<int> dep(n);
auto dfs = [&](auto & self, int u) -> void {
for (auto & v : adj[u]) {
dep[v] = dep[u] + 1;
fa[v][0] = u;
for (int i = 1; i <= lg; ++ i) {
fa[v][i] = fa[fa[v][i - 1]][i - 1];
}
self(self, v);
}
};
dfs(dfs, 0);
std::vector<int> st(n, 0);
st[0] = 1;
for (auto & [v, l, r] : a) {
if (st[v]) {
std::cout << l << "\n";
return;
}
int u = v;
for (int i = lg; i >= 0; -- i) {
if (!st[fa[u][i]]) {
u = fa[u][i];
}
}
int d = dep[v] - dep[u];
if (r - l >= d) {
std::cout << l + d << "\n";
return;
}
d -= r - l;
u = v;
for (int i= lg; i >= 0; -- i) {
if (dep[u] - dep[fa[u][i]] <= d) {
d -= dep[u] - dep[fa[u][i]];
u = fa[u][i];
}
}
while (!st[u]) {
st[u] = 1;
u = fa[u][0];
}
}
std::cout << -1 << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
// std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
J. Jetton
一开始一直在观察性质,不过没什么用。。。
发现小的数每次都会乘二,猜测有解不会进行很多轮,直接暴力模拟1000次就过了。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
i64 x, y;
std::cin >> x >> y;
if (x == y) {
std::cout << 1 << "\n";
return;
}
if ((x + y) % 4) {
std::cout << -1 << "\n";
return;
}
if ((x + y) / 4 % 2) {
if (x % 2 == 0 && y % 2 == 0) {
std::cout << -1 << "\n";
return;
}
}
int ans = 0;
int cnt = 0;
while (x != 0 && y != 0) {
if ( ++ cnt > 1000) {
std::cout << -1 << "\n";
return;
}
if (x > y) {
std::swap(x, y);
}
++ ans;
y -= x;
x += x;
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}