牛客周赛 Round 77
A. 时间表
输出即可。
点击查看代码
void solve() {
std::string s[] = {"20250121", "20250123", "20250126", "20250206", "20250208", "20250211"};
int n;
std::cin >> n;
std::cout << s[n - 1] << "\n";
}
B. 数独数组
题意:一个数组的任意一个长度为\(9\)的连续子数组都是\(1\)到\(9\)的排列。有没有一种摆放方式符合。
我们应该\(\{1, 2, ..., 8, 9, 1, 2 ...\}\)这样循环摆下去,才能保证最多的子数组满足,那么每个数组的出现次数要么是\(\lfloor \frac{n}{9} \rfloor\),要么是\(\lceil\frac{n}{9} \rceil\)。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n), cnt(10);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
++ cnt[a[i]];
}
for (int i = 1; i <= 9; ++ i) {
if (cnt[i] != n / 9 && cnt[i] != (n + 8) / 9) {
std::cout << "NO\n";
return;
}
}
std::cout << "YES\n";
}
C. 小红走网格
题意:一个网格里,每次分别可以往上下左右走对应的\(a, b, c, d\)步,问能不能到\((x, y)\)。
其实是\(k_1a + k_2b = y, k_3c + k_4d = x\),那么要求\(gcd(a, b) | y\) 和 \(gcd(c, d) | x\)。
点击查看代码
void solve() {
int x, y, a, b, c, d;
std::cin >> x >> y >> a >> b >> c >> d;
if (y % std::gcd(a, b) == 0 && x % std::gcd(c, d) == 0) {
std::cout << "YES\n";
} else {
std::cout << "NO\n";
}
}
D. 隐匿社交网络
题意:\(n\)个数,如果两个数之间二进制下有一位上都是\(1\),那么他们就是一个集合的,问最大的集合有多少人。
明显是并查集,以每一位为一个集合,对应每个数合并其所有\(1\)的位的集合就行。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<i64> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::vector<int> fa(60), cnt(60, 0);
std::iota(fa.begin(), fa.end(), 0);
std::function<int(int)> find = [&](int x) -> int {
return x == fa[x] ? x : fa[x] = find(fa[x]);
};
for (int i = 0; i < n; ++ i) {
i64 x = -1;
for (i64 j = 0; j < 60; ++ j) {
if (a[i] >> j & 1) {
if (x == -1) {
x = j;
} else {
if (find(x) == find(j)) {
continue;
}
cnt[find(x)] += cnt[find(j)];
fa[find(j)] = find(x);
}
}
}
cnt[find(x)] += 1;
}
int ans = 0;
for (int i = 0; i < 60; ++ i) {
if (find(i) == i) {
ans = std::max(ans, cnt[i]);
}
}
std::cout << ans << "\n";
}
E. 1or0
题意:一个\(01\)串,每次问一个区间,回答这个区间有多少个子区间至少有\(1\)个\(1\)。
我们记\(last_i\)为左边离\(i\)最近的\(1\)的位置,\(sum_i = \sum_{j=1}^{i} last_i\)。记\(suf_i\)为右边离\(i\)最近的\(1\)的位置。
那么当询问一个区间\([l, r]\)时,我们可得\(sum_r - sum_{l-1}\),但这个计算得答案区间左端点会在\(l\)左边,所以我们要减去这种情况,那么看\(last_l\)和\(suf_l\)在哪,那么\(last_i\)在\(l\)之后的有\(r - l + 1 - (suf_l - l)\)个,在\(l\)之前的有\(suf_l - l\)个,要减去的贡献为\((r-l+1-(suf_l1-l))*(l-1)+(suf_l-l)*last_l\)。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
std::vector<i64> sum(n + 1), last(n + 1);
for (int i = 1; i <= n; ++ i) {
if (s[i - 1] == '1') {
last[i] = i;
} else {
last[i] = last[i - 1];
}
sum[i] = sum[i - 1] + last[i];
}
std::vector<i64> suf(n + 2, n + 1);
for (int i = n; i >= 1; -- i) {
if (s[i - 1] == '0') {
suf[i] = suf[i + 1];
} else {
suf[i] = i;
}
}
int q;
std::cin >> q;
while (q -- ) {
i64 l, r;
std::cin >> l >> r;
i64 ans = sum[r] - sum[l - 1];
i64 L = last[l], R = std::min(r + 1, suf[l]);
ans -= ((i64)r - l + 1 - (R - l)) * (l - 1);
ans -= ((i64)R - l) * L;
std::cout << std::max(0ll, ans) << "\n";
}
}
F. 计树
题意:一棵树,会随机从集合里选两个点,问每个点可能是这两个点的\(lca\)的情况有多少种。
考虑树形\(dp\),\(f_u\)表示答案,\(cnt_u\)表示以\(u\)为根的子树下有多少个可选点。那么一个让\(u\)每个子树单独考虑,只要两个点在不同的子树下那么\(lca\)就是\(u\),那么答案是让每个子树的可选点两两相乘,我们知道\(\sum_{i=1}^{n}\sum_{j=1}^{n} a_ia_j = (\sum_{i=1}^{n} a_i)^2\),那么我们要求的答案就是\((\sum_{v \in u} cnt_v)^2 - \sum_{v \in u} cnt_v^2\),如果\(u\)本身就是可选点,还有加上\(\sum_v cnt_v\)以及加上两个点都选自己的情况。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<std::vector<int> > adj(n);
for (int i = 1; i < n; ++ i) {
int u, v;
std::cin >> u >> v;
-- u, -- v;
adj[u].push_back(v);
adj[v].push_back(u);
}
int k;
std::cin >> k;
std::vector<int> a(n);
for (int i = 0; i < k; ++ i) {
int x;
std::cin >> x;
-- x;
a[x] = 1;
}
std::vector<i64> f(n), cnt(n);
auto dfs = [&](auto self, int u, int fa) -> void {
i64 sum = 0;
for (auto & v : adj[u]) {
if (v == fa) {
continue;
}
self(self, v, u);
cnt[u] += cnt[v];
sum += cnt[v] * cnt[v];
}
f[u] = (cnt[u] * cnt[u] - sum) + cnt[u] * a[u] * 2 + a[u];
cnt[u] += a[u];
};
dfs(dfs, 0, -1);
for (int i = 0; i < n; ++ i) {
std::cout << f[i] << " \n"[i == n - 1];
}
}