Codeforces Round 938 (Div. 3)
写在前面
比赛地址:https://codeforces.com/contest/1955。
练手速的。
唉状态太垃圾了纯唐氏一个
A
签到。
复制复制// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long //============================================================= //============================================================= //============================================================= int main() { //freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); int T; std::cin >> T; while (T --) { int n, a, b; std::cin >> n >> a >> b; if (b >= 2 * a) { std::cout << n * a << "\n"; } else { int k = n / 2, r = n % 2; std::cout << k * b + r * a << "\n"; } } return 0; }
B
枚举。
记录给定矩阵中每个数出现次数,选择其中最小值为 ,构造出合法矩阵的唯一形态并检查每个数出现次数是否合法即可。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 510; //============================================================= //============================================================= std::map <LL, int> cnt; int a[kN][kN]; std::vector <int> b; //============================================================= int main() { //freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); int T; std::cin >> T; while (T --) { int n, c, d; std::cin >> n >> c >> d; cnt.clear(); b.clear(); for (int i = 1; i <= n * n; ++ i) { int x; std::cin >> x; cnt[x] = cnt[x] + 1; b.push_back(x); } std::sort(b.begin(), b.end()); int a11 = b[0], flag = 1; for (int i = 1; i <= n; ++ i) { for (int j = 1; j <= n; ++ j) { LL x = a11 + 1ll * (i - 1) * c + 1ll * (j - 1) * d; if (!cnt.count(x) || cnt[x] == 0) flag = 0; cnt[x] = cnt[x] - 1; } } std::cout << (flag ? "YES\n" : "NO\n"); } return 0; }
C
模拟。
模拟地每次将左右两端血量较少的一个消灭即可。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 2e5 + 10; //============================================================= int n, a[kN]; LL k; //============================================================= //============================================================= int main() { // freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); int T; std::cin >> T; while (T --) { std::cin >> n; std::cin >> k; for (int i = 1; i <= n; ++ i) std::cin >> a[i]; int p[2] = {1, n}, now = 0, ans = 0; while (k && p[0] <= p[1]) { LL need = 0; if (p[0] == p[1]) need = a[p[0]]; else if (a[p[now]] <= a[p[now ^ 1]]) need = 2ll * a[p[now]] - 1; else need = 2ll * a[p[now ^ 1]]; if (need > k) break; k -= need, ++ ans; if (p[0] == p[1]) break; else if (a[p[now]] <= a[p[now ^ 1]]) { a[p[now ^ 1]] -= a[p[now]] - 1, a[p[now]] = 0; if (now == 0) ++ p[0]; else -- p[1]; now = now ^ 1; } else { a[p[now]] -= a[p[now ^ 1]], a[p[now ^ 1]] = 0; if (now == 0) -- p[1]; else ++ p[0]; } } std::cout << ans << "\n"; } return 0; } /* 1 2 5 5 2 */
D
枚举。
记录每个数在 中的出现次数。
顺序枚举 中所有长度为 的区间,同时维护其中有多少在 中的数,若大于 则该区间有贡献。
注意若某个数在区间中出现次数大于其在 中出现次数,则超出部分无贡献。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 1e6 + 10; //============================================================= int n, m, k, cnt, ans, a[kN], b[kN]; int cnta[kN], cntb[kN]; //============================================================= //============================================================= int main() { // freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); int T; std::cin >> T; while (T --) { std::cin >> n >> m >> k; cnt = ans = 0; for (int i = 1; i <= n; ++ i) { std::cin >> a[i]; cnta[a[i]] = cntb[a[i]] = 0; } for (int i = 1; i <= m; ++ i) { std::cin >> b[i]; ++ cntb[b[i]]; } for (int i = 1; i <= m; ++ i) { if (cntb[a[i]] && cnta[a[i]] < cntb[a[i]]) ++ cnt; ++ cnta[a[i]]; } for (int l = 1, r = m; r <= n; ++ l, ++ r) { if (cnt >= k) ++ ans; if (r == n) break; -- cnta[a[l]]; if (cntb[a[l]] && cnta[a[l]] < cntb[a[l]]) -- cnt; if (cntb[a[r + 1]] && cnta[a[r + 1]] < cntb[a[r + 1]]) ++ cnt; ++ cnta[a[r + 1]]; } std::cout << ans << "\n"; } return 0; }
E
贪心,枚举。
给定限制 ,考虑枚举修改区间长度 并检查是否合法。
考虑贪心地进行修改,每次选择字符串中最左侧第一个 0,并以该位置为左端点进行一次修改,可以发现若 合法则这样一定构造出全 1 串。
然而直接暴力实现是 的,但是发现每次选择的 0 的位置一定是递增的,且一个位置在若干次修改后是否为 0 仅与其初始值与该位置被修改次数(即被区间覆盖次数)有关,于是考虑在顺序枚举位置并进行区间修改时,差分维护每个位置被修改次数即可判断某个位置是否需要被修改。
单次检查时间复杂度变为 级别,总时间复杂度 级别。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 5010; //============================================================= int n, d[kN]; std::string s, t; //============================================================= bool check(int len_) { t = s; for (int i = 0; i < n; ++ i) d[i] = 0; for (int i = 0; i < n; ++ i) { d[i] += d[i - 1]; int ch = s[i] - '0'; if (d[i] % 2 == 1) ch ^= 1; if (ch == 0) { if (i <= n - len_) ++ d[i], -- d[i + len_]; else return false; } } return true; } //============================================================= int main() { //freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); int T; std::cin >> T; while (T --) { std::cin >> n; std::cin >> s; for (int i = n; i; -- i) { if (check(i)) { std::cout << i << "\n"; break; } } } return 0; }
F
结论。
发现对于某个游戏状态 ,Bob 获胜等价于:
于是考虑先将 调整为最接近的 2 的倍数,然后构造出所有仅考虑 的 Bob 胜利游戏状态,然后再不断调整 直至游戏结束。则答案即为 的胜利游戏状态数 + 。
赛时一看数据范围这么小懒得想了,直接写了个三维 DP,记 表示 时的胜利游戏状态数量,则有转移:
地预处理后 回答询问即可,答案即为 。
然而进一步地考虑,实际上连预处理都不需要。可以发现一种最优的操作方案是先将 均调整到最接近的 2 的倍数,然后依次将 调整到 0,即仅关注 时的贡献。可以证明若通过调整使得 之后,若保持该性质并获取贡献,可得到的总贡献肯定不会多于上述方案;若再调整为 则贡献一定会更少。
注意若初始时 即为合法方案,则需要令答案加 1。
综上,按照上述方案答案即为 。
以下为赛时代码。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 210; //============================================================= LL f[kN][kN][kN]; //============================================================= //============================================================= int main() { // freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); for (int i = 0; i <= 200; ++ i) { for (int j = 0; j <= 200; ++ j) { for (int k = 0; k <= 200; ++ k) { int d = (((i + 1) % 2 + (j % 2)) == (2 * (k % 2))); f[i + 1][j][k] = std::max(f[i + 1][j][k], f[i][j][k] + d); d = (((i % 2) + ((j + 1) % 2)) == (2 * (k % 2))); f[i][j + 1][k] = std::max(f[i][j + 1][k], f[i][j][k] + d); d = (((i % 2) + (j % 2)) == (2 * ((k + 1) % 2))); f[i][j][k + 1] = std::max(f[i][j][k + 1], f[i][j][k] + d); } } } int T; std::cin >> T; while (T --) { int p[4]; for (int i = 0; i < 4; ++ i) std::cin >> p[i]; // LL ans = p[4] / 2; std::cout << (f[p[0]][p[1]][p[2]] + (p[3] / 2)) << "\n"; } return 0; }
G
枚举,数论。
典中典之枚举 。
考虑如何检查一个数 能否成为从 的路径上的数的公因数,等价于检查是否存在一条路径使所有数均可整除 ,从 开始 BFS 钦定只能经过可整除 的数,检查是否可以到达 即可。
发现答案必然为 的因数,由结论[1]可知不大于 的数其因数数量不超过 级别,于是直接枚举因数并大力检查即可。
总时间复杂度 级别,其中 。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long #define pii std::pair<int,int> #define mp std::make_pair const int kN = 110; const int kM = 1e6; //============================================================= int n, m, a[kN][kN]; bool vis[kN][kN]; //============================================================= int gcd(int x_, int y_) { return y_ ? gcd(y_, x_ % y_) : x_; } bool check(int prod_) { std::queue <pii> q; for (int i = 1; i <= n; ++ i) { for (int j = 1; j <= m; ++ j) { vis[i][j] = 0; } } q.push(mp(1, 1)); while (!q.empty()) { pii u = q.front(); q.pop(); int x = u.first, y = u.second; if (vis[x][y]) continue; vis[x][y] = 1; if (x + 1 <= n && a[x + 1][y] % prod_ == 0) { if (!vis[x + 1][y]) q.push(mp(x + 1, y)); } if (y + 1 <= m && a[x][y + 1] % prod_ == 0) { if (!vis[x][y + 1]) q.push(mp(x, y + 1)); } } return vis[n][m]; } //============================================================= int main() { // freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); int T; std::cin >> T; while (T --) { std::cin >> n >> m; for (int i = 1; i <= n; ++ i) { for (int j = 1; j <= m; ++ j) { std::cin >> a[i][j]; } } int lim = gcd(a[1][1], a[n][m]), ans = 1; for (int i = 1; i * i <= lim; ++ i) { if (lim % i) continue; if ((lim / i) > ans && check(lim / i)) ans = lim / i; if (i > ans && check(i)) ans = i; } std::cout << ans << "\n"; } return 0; }
H
费用流。
要是没有防御塔必须攻击半径不同的限制将使纯恼弹题,然而有,则需要考虑如何分配攻击半径。
指数增长数量级太快了,并且 ,则当攻击范围较扩大时伤害减怪物血量将很快衰减到负值,则实际上有贡献的攻击半径数量级相当小,应有 ,考虑取最大半径为 。又需要使得每座防御塔均有攻击半径,有一种满流的美感,考虑费用流。
具体地,建立源点 与汇点 :
- 对于所有 座防御塔,与攻击半径 分别建立节点。
- 源点向所有防御塔连边,容量为 1,费用为 0。
- 所有攻击半径向汇点连边,容量为 1,费用为 0。
- 对于所有防御塔 ,枚举确定该防御塔攻击半径为 时可覆盖路径位置数量 ,则从防御塔 向攻击半径 连边,容量为 1,费用为 。
建图后跑最小费用最大流,输出最小费用的相反数即为答案。
总点数为 级别,总边数为 级别,通过建图方式可知最大流至多仅有 ,则总时间复杂度上界为 ,然而远远到不了上界,实际运行效率较高。
代码中取了 。
同样考虑如何分配攻击半径,也可以状压 DP 解决此题,详见其他题解。
//By:Luckyblock /* */ #include <bits/stdc++.h> #define LL long long #define pii std::pair<int,int> #define mp std::make_pair const int kMap = 60; const int kN = 2e5 + 10; const int kM = 2e6 + 10; const int kR = 15; const LL kInf = 1e18 + 2077; //============================================================= int n, m, k, s, t; char map[kMap]; std::vector <pii> road; int nodenum, edgenum = 1, head[kN], v[kM], ne[kM]; LL w[kM], c[kM], dis[kN], maxflow, mincost; int cur[kN]; bool vis[kN]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = - 1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + ch - '0'; return f * w; } void Add(int u_, int v_, LL w_, LL c_) { v[++ edgenum] = v_; w[edgenum] = w_; c[edgenum] = c_; ne[edgenum] = head[u_]; head[u_] = edgenum; v[++ edgenum] = u_; w[edgenum] = 0; c[edgenum] = -c_; ne[edgenum] = head[v_]; head[v_] = edgenum; } bool Spfa() { std::queue <int> q; for (int i = 0; i <= t; ++ i) dis[i] = kInf; dis[s] = 0, vis[s] = 1; q.push(s); while (!q.empty()) { int u_ = q.front(); q.pop(); vis[u_] = 0; for (int i = head[u_]; i; i = ne[i]) { int v_ = v[i]; LL w_ = w[i], c_ = c[i]; if (w_ > 0 && dis[u_] + c_ < dis[v_]) { dis[v_] = dis[u_] + c_; if (!vis[v_]) q.push(v_), vis[v_] = 1; } } } return dis[t] != kInf; } LL Dfs(int u_, LL into_) { if (u_ == t || into_ == 0) return into_; vis[u_] = 1; LL out = 0; for (int &i = cur[u_]; i && into_; i = ne[i]) { int v_ = v[i]; LL w_ = w[i], c_ = c[i]; if (!vis[v_] && dis[v_] == dis[u_] + c_ && w_ > 0) { LL ret = Dfs(v_, std::min(into_ - out, w_)); if (ret) { w[i] -= ret, w[i ^ 1] += ret; out += ret, into_ -= ret; mincost += c_ * ret; } } } vis[u_] = 0; return out; } LL MCMF() { LL ret = 0, x; while (Spfa()) { for (int i = 0; i <= t; ++ i) cur[i] = head[i]; while ((x = Dfs(s, kInf))) ret += x; } return ret; } double distance(int x0_, int y0_, int x1_, int y1_) { return 1.0 * (x0_ - x1_) * (x0_ - x1_) + 1.0 * (y0_ - y1_) * (y0_ - y1_); } void Init() { for (int i = 0; i <= t; ++ i) head[i] = 0; edgenum = 1; mincost = maxflow = 0; road.clear(); n = read(), m = read(), k = read(); for (int i = 1; i <= n; ++ i) { scanf("%s", map + 1); for (int j = 1; j <= m; ++ j) { if (map[j] == '#') road.push_back(mp(i, j)); } } s = 0, t = k + kR + 10; for (int i = 1; i <= kR; ++ i) { Add(k + i, t, 1, 0); } for (int i = 1; i <= k; ++ i) { int x = read(), y = read(), p = read(); std::vector <double> dis; for (auto pos: road) { dis.push_back(distance(x, y, pos.first, pos.second)); } std::sort(dis.begin(), dis.end()); int pos = 0, sz = dis.size(); LL pow3 = 3, sum = 0; Add(s, i, 1, 0); for (int r = 1; r <= kR; ++ r) { while (pos < sz && r * r >= dis[pos]) { sum += p, ++ pos; } Add(i, k + r, 1, -std::max(0ll, sum - pow3)); pow3 *= 3ll; } } } //============================================================= int main() { // freopen("1.txt", "r", stdin); int T = read(); while (T --) { Init(); maxflow = MCMF(); printf("%lld\n", -mincost); } return 0; } /* 1 2 2 1 #. ## 1 2 1 1 3 3 2 #.. ##. .## 1 2 4 3 1 3 */
写在最后
学到了什么:
- 典中典。
- H:注意建反边,令容量为 0,费用为相反数。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通