牛客周赛 Round 90
A. 真爱粉Tk(一)
点击查看代码
void solve() {
i64 n;
std::cin >> n;
if (n * 10 % 25 == 0) {
std::cout << "Yes\n";
} else {
std::cout << "No\n";
}
}
B. 真爱粉Tk(二)
题意:一个只包含\(2, 5\)的字符串,你每次交换两个位置的数,使得最终没有\(25\)这个子序列。求最小操作数。
实际是让\(5\)都在左边,\(2\)都在右边。
那么两个指针分别从前面和后面枚举,找前面的\(2\)和后面的\(5\)交换。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
int l = 0, r = n - 1;
int ans = 0;
while (l < r) {
while (l < r && s[l] == '5') {
++ l;
}
while (r > l && s[r] == '2') {
-- r;
}
if (l < r) {
++ ans;
++ l, -- r;
}
}
std::cout << ans << "\n";
}
C. Tk的构造数组
题意:给你\(a, b\),重新排列\(b\),使得\(\sum_{i=1}^{n} a_i \times b_i \times i\)最大。
把\(a_i\)看作\(a_i \times i\),那么问题变成使得\(\sum_{i=1}^{n} a_i \times b_i\)最大。
问你让最大的数和最大的匹配,次大的和次大的匹配..依次从大到小排就行。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<std::pair<i64, int>> a(n);
std::vector<i64> b(n);
for (int i = 0; i < n; ++ i) {
i64 x;
std::cin >> x;
a[i] = {x * (i + 1), i};
}
for (int i = 0; i < n; ++ i) {
std::cin >> b[i];
}
std::ranges::sort(a, std::greater<>());
std::ranges::sort(b, std::greater<>());
std::vector<int> ans(n);
for (int i = 0; i < n; ++ i) {
ans[a[i].second] = b[i];
}
for (int i = 0; i < n; ++ i) {
std::cout << ans[i] << " \n"[i == n - 1];
}
}
D. 真爱粉Tk(三)
题意:把\(a\)分成\(k\)份,使得每一份的数字变成字符串连接后\(25\)这个序列出现次数最大的最小。
最大最小,考虑二分。
先把每个\(a_i\)转成字符串。那么假设选择一个部分最多\(mid\)个\(25\)子序列,则可以每次从左端点开始向右移,直到\(25\)出现次数大于\(mid\),就可以求出来这一段。那么最终分出的段数小于等于\(k\)就可行。
求\(25\)作为子序列出现的个数,就是从前往后记录\(2\)的值,每次遇到\(5\)就累加\(2\)的个数。
点击查看代码
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<std::string> a(n);
for (int i = 0; i < n; ++ i) {
int x;
std::cin >> x;
a[i] = std::to_string(x);
}
auto check = [&](i64 x) -> bool {
int cnt = 0;
for (int i = 0; i < n; ++ i) {
i64 pre = 0, sum = 0;
int j = i;
while (j < n && sum <= x) {
for (auto & c : a[j]) {
if (c == '2') {
++ pre;
} else if (c == '5') {
sum += pre;
}
}
if (sum > x) {
break;
}
++ j;
}
if (i == j) {
return false;
}
i = j - 1;
++ cnt;
}
return cnt <= k;
};
i64 l = 0, r = 1e18;
while (l < r) {
i64 mid = l + r >> 1ll;
if (check(mid)) {
r = mid;
} else {
l = mid + 1;
}
}
std::cout << l << "\n";
}
E. Tk的染色树
题意:一棵树,每次选择一个点花费\(2\times a_i\)把它染成黑色,或者选择\(i, j\)花费\(a_i + a_j\)把它们的路径上的点都染成黑色。求全部点染成黑色的最小花费。
一开始以为是树形\(dp\),卡了好久。
其实就是考虑叶子怎么选择。如果有偶数个叶子,那么这些叶子两两匹配,就可以覆盖所有点,代价为叶子的和。
如果有奇数个叶子,则同样两两匹配,剩下一个和树里最小的点操作,代价是叶子和加最小值。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<i64> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
if (n == 1) {
std::cout << a[0] * 2 << "\n";
return;
}
std::vector<int> deg(n);
for (int i = 1; i < n; ++ i) {
int u, v;
std::cin >> u >> v;
-- u, -- v;
++ deg[u]; ++ deg[v];
}
i64 sum = 0, cnt = 0, min = 1e18;
for (int i = 0; i < n; ++ i) {
if (deg[i] == 1) {
sum += a[i];
++ cnt;
}
min = std::min(a[i], min);
}
i64 ans = 1e18;
if (cnt % 2 == 0) {
ans = std::min(ans, sum);
} else {
ans = std::min(ans, sum + min);
}
std::cout << ans << "\n";
}
F. Tk的排列间异或
题意:给你一个长度为\(n\)的排列\(a\),构造一个排列\(b\),使得\(\sum_{i=1}^{n} a_i \oplus b_i\)最大。\(n\)是偶数。
给出的排列并不重要,我们只需要求出\(i\)这个数和哪个数匹配就行。
考虑从大到小枚举\(i\),找和\(i\)异或最大的小于它的数就行。然后标记一些这个数被选过了,那么到了这个数直接跳过。
从大到小枚举不会有两个数选同一个数,而从小到大枚举可以有多个数选同一个数。
怎么找小于\(x\)的异或最大的数?从高位往低位枚举,如果这一位\(x\)是\(1\)则不用管,如果是\(0\),则看加上这一位会不会超过\(x\)。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n + 1);
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i];
}
std::vector<int> p(n + 1), st(n + 1);
for (int i = n; i >= 1; -- i) {
if (st[i]) {
continue;
}
int x = 0;
for (int j = 20; j >= 0; -- j) {
if (~i >> j & 1) {
if (x + (1 << j) <= i) {
x += 1 << j;
}
}
}
st[x] = 1;
p[i] = x;
p[x] = i;
}
for (int i = 1; i <= n; ++ i) {
std::cout << p[a[i]] << " \n"[i == n];
}
}

浙公网安备 33010602011771号