VP Educational Codeforces Round 9
A. Grandma Laura and Apples
题意:一开始你有一个\(n\),然后经过若干次运算,直到\(n = 0\),如果当前\(n\)是偶数,则直接除\(2\),获得\(\frac{n}{2} \times m\)的钱,如果当前\(n\)是奇数,则获得\(\frac{n}{2} \times m\)的钱,\(n = \lfloor \frac{n}{2} \rfloor\),保证\(m\)是偶数,现在给出每个运算时是奇数还是偶数,求一共赚了多少钱。
先通过反推求出\(n\)来,最后一个的时候\(n\)一定等于\(1\),那么如果当前运算前是奇数,\(n = n \times 2 + 1\),否则\(n = n \times2\)。然后再正着模拟一遍就行。
点击查看代码
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<std::string> s(n);
for (int i = 0; i < n; ++ i) {
std::cin >> s[i];
}
std::reverse(s.begin(), s.end());
i64 sum = 1;
for (int i = 1; i < n; ++ i) {
if (s[i] == "halfplus") {
sum = sum * 2 + 1;
} else {
sum = sum * 2;
}
}
i64 ans = 0;
std::reverse(s.begin(), s.end());
for (int i = 0; i < n; ++ i) {
if (s[i] == "halfplus") {
ans = ans + sum / 2 * m + m / 2;
} else {
ans = ans + sum / 2 * m;
}
sum /= 2;
}
std::cout << ans << "\n";
}
B. Alice, Bob, Two Teams
题意:给你一个\(AB\)串,每个位置都有一个价值,你可以获得所有\(b\)位置上的价值,现在你可以将一个前缀或者一个后缀位置上的数都改变,求最大价值。
预处理一个\(a\)和\(b\)的价值前缀和,然后枚举翻转的前缀和后缀就行。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::string s;
std::cin >> s;
std::vector<i64> suma(n + 1), sumb(n + 1);
for (int i = 0; i < n; ++ i) {
suma[i + 1] = suma[i] + (s[i] == 'A' ? a[i] : 0);
sumb[i + 1] = sumb[i] + (s[i] == 'B' ? a[i] : 0);
}
i64 ans = sumb[n];
for (int i = n; i >= 1; -- i) {
ans = std::max(ans, sumb[i - 1] + suma[n] - suma[i - 1]);
}
for (int i = 1; i <= n; ++ i) {
ans = std::max(ans, sumb[n] - sumb[i] + suma[i]);
}
std::cout << ans << "\n";
}
C. The Smallest String Concatenation
题意:给你\(n\)个字符串,你要把它们拼接成一个字符串,使得这个字符串字典序最小。
直接排序即可,排序判断就是直接看谁在前面是组成的字符串更小。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<std::string> s(n);
for (int i = 0; i < n; ++ i) {
std::cin >> s[i];
}
std::sort(s.begin(), s.end(), [&](std::string & s, std::string & t) {
return s + t < t + s;
});
for (auto & x : s) {
std::cout << x;
}
std::cout << "\n";
}
D. Longest Subsequence
题意:给你一个数组,你要选尽可能多少数使得它们的\(lcm\)小于等于\(m\),求最多选多少数。
发现\(m\)只有\(1e6\),那么可以预处理每个数的因子,然后把每个数的倍数加一,这样就得到了每个数作为\(lcm\)是可以选多少数,从小到大枚举即可。注意如果是\(2, 2, 4\),那么\(4\)和\(8\)的值都是\(3\),但实际上这三个数的\(lcm\)不是\(8\),但我们是从小到大枚举的,所以会选择\(4\)。
点击查看代码
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::vector<std::vector<int>> factor(m + 1);
for (int i = 1; i <= m; ++ i) {
for (int j = i; j <= m; j += i) {
factor[j].push_back(i);
}
}
std::vector<int> cnt(m + 1);
for (int i = 0; i < n; ++ i) {
if (a[i] <= m) {
cnt[a[i]] += 1;
}
}
std::vector<int> sum(m + 1);
for (int i = 1; i <= m; ++ i) {
for (auto & j : factor[i]) {
sum[i] += cnt[j];
}
}
int max = 0, p = 0;
for (int i = 1; i <= m; ++ i) {
if (sum[i] > max) {
max = sum[i];
p = i;
}
}
if (p == 0) {
std::cout << 1 << " " << 0 << "\n";
return;
}
std::vector<int> ans;
for (int i = 0; i < n; ++ i) {
if (p % a[i] == 0) {
ans.push_back(i);
}
}
std::cout << p << " " << ans.size() << "\n";
for (auto & x : ans) {
std::cout << x + 1 << " \n"[x == ans.back()];
}
}
E. Thief in a Shop
题意:\(n\)个物品可以选无限个,你要选恰好\(k\)个物品,求所有组合中可以出现的所有总价值。
很显然的完全背包,可以记\(f[i][j]\)表示前\(i\)个物品凑成\(j\)价值最少需要几个物品。但发现我们求的是最小,如果\(j != k\)我们不能判断是不是还可以选一些物品凑成\(k\)个,那么可以把所有物品都减去最小值,这之后每个价值需要凑成的物品数小于等于\(k\)个就可以拿若干个\(0\)价值的物品来凑。假设之前能被凑出\(k\)个的物品价值为\(m\),因为每个物品的价值都减去了\(v\),那么在变化后的\(dp\)方程里这个值是\(m - k \times v\),那么这个值还是能被正确求出来,因为这只是所有物品和总价值的都减去了相同值,肯定还是能凑出来,那如果变化后的\(f[i][j] < k\),一定能用\(0\)价值的物品凑成\(k\)个吗?这个时候价值其实\(j + f[i][j] \times v\),如果我们加上\(k - f[i][j]\)个\(v\)就能凑成\(k\)个,然后价值恰好是\(j + k \times v\)。
点击查看代码
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
int min = *std::min_element(a.begin(), a.end());
for (auto & x : a) {
x -= min;
}
const int N = 1e6 + 5, inf = 1e9;
std::vector<int> f(N, inf);
f[0] = 0;
for (int i = 0; i < n; ++ i) {
for (int j = a[i]; j < N; ++ j) {
f[j] = std::min(f[j], f[j - a[i]] + 1);
}
}
for (int i = 0; i < N; ++ i) {
if (f[i] <= k) {
std::cout << i + k * min << " ";
}
}
std::cout << "\n";
}
F. Magic Matrix
题意:给你一个矩阵,判断是不是满足三个条件:
- 对于\(i \in [1, n], a[i][i] = 0\)。
- 对于\(i, j \in [1, n], a[i][j] = a[j][i]\)。
- 对于\(i, j \in [1, n], a[i][j] <= \min_{k=1}^{n} \max(a[i][k], a[j][k])\)。
如果满足前两个条件,那么第三个条件可以变化为:\(a[i][j] <= \min_{k=1}^{n} \max(a[i][k], a[k][j]) ==> a[i][j] <= \min_{k=1}^{n} \sum_{l=1}^{n} \max(a[i][k], a[k][l], a[l][j])\),同理这三项还可以一直拆下去,最终是\(a[i][j] <= \max(a[i][t_1], a[i][t_2], ..., a[i][t_m], a[i][j])\),意味着对于任意一条\(i\)到\(j\)的路径上的最大值都大于等于\(a[i][j]\)。
那么我们可以类似\(kruskal\),将边从小到大排序,然后对于边值相同的一块处理,如果这些边里有一条边连接的两个点在之前以及是在一个联通块里了,那么说明它们之间已经有路径了,且这个路径上所有边权都小于当前边边权,则输出NO,否则就把这些边都加进联通块里就行。可以用并查集维护联通块。
点击查看代码
struct DSU {
std::vector<int> fa, cnt;
DSU(int _n) {
init(_n);
}
void init(int _n) {
fa.assign(_n, 0);
cnt.assign(_n, 1);
std::iota(fa.begin(), fa.end(), 0);
}
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
bool merge(int x, int y) {
x = find(x), y = find(y);
if (x == y) {
return false;
}
fa[y] = x;
cnt[x] += cnt[y];
return true;
}
bool same(int x, int y) {
return find(x) == find(y);
}
int size(int x) {
return cnt[find(x)];
}
};
void solve() {
int n;
std::cin >> n;
std::vector a(n, std::vector<int>(n));
for (int i = 0; i < n; ++ i) {
for (int j = 0; j < n; ++ j) {
std::cin >> a[i][j];
}
}
for (int i = 0; i < n; ++ i) {
if (a[i][i] != 0) {
std::cout << "NOT MAGIC\n";
return;
}
for (int j = 0; j < n; ++ j) {
if (a[i][j] != a[j][i]) {
std::cout << "NOT MAGIC\n";
return;
}
}
}
std::vector<std::array<int, 3>> edges;
for (int i = 0; i < n; ++ i) {
for (int j = 0; j < n; ++ j) {
if (i == j) {
continue;
}
edges.push_back({a[i][j], i, j});
}
}
std::sort(edges.begin(), edges.end());
DSU dsu(n);
for (int i = 0; i < edges.size(); ++ i) {
int j = i;
while (j < edges.size() && edges[j][0] == edges[i][0]) {
++ j;
}
-- j;
for (int k = i; k <= j; ++ k) {
if (dsu.same(edges[k][1], edges[k][2])) {
std::cout << "NOT MAGIC\n";
return;
}
}
for (int k = i; k <= j; ++ k) {
dsu.merge(edges[k][1], edges[k][2]);
}
i = j;
}
std::cout << "MAGIC\n";
}