AtCoder Beginner Contest 327
2023.11.08 update: 更新了G
A - ab (abc327 A)
题目大意
给定一个字符串,问是否包含 ab
或ba
。
解题思路
遍历判断即可。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; string s; cin >> n >> s; bool ok = false; for (int i = 1; i < n; ++i) { ok |= (s[i] == 'a' && s[i - 1] == 'b'); ok |= (s[i - 1] == 'a' && s[i] == 'b'); } if (ok) cout << "Yes" << '\n'; else cout << "No" << '\n'; return 0; }
B - A^A (abc327 B)
题目大意
给定,问是否存在 使得 。
解题思路
由于指数爆炸的缘故,的范围不会很大,枚举,看是否为 ,然后用不断除以 ,看最后除的次数是否等于。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); LL b; cin >> b; int ans = -1; for (int i = 2; i <= 20; ++i) { if (b % i != 0) continue; LL x = b; int cnt = 0; while (x > 1) { x /= i; ++cnt; } if (cnt == i) { ans = i; break; } } if (b == 1) ans = 1; cout << ans << '\n'; return 0; }
C - Number Place (abc327 C)
题目大意
给定一个的网格,问是否符合数独局面。
数独局面,即每行 ,每列 ,每个 的格子有 。
解题思路
按照条件,逐行、逐列、逐格子分别判断即可。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); array<array<int, 9>, 9> a; for (auto& i : a) for (auto& j : i) cin >> j; auto ok = [&]() { for (auto& i : a) { if (set<int>(i.begin(), i.end()).size() != 9) return false; } for (int i = 0; i < 9; ++i) { set<int> cnt; for (int j = 0; j < 9; ++j) { cnt.insert(a[j][i]); } if (cnt.size() != 9) return false; } for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { set<int> cnt; for (int x = 0; x < 3; ++x) for (int y = 0; y < 3; ++y) { cnt.insert(a[i * 3 + x][j * 3 + y]); } if (cnt.size() != 9) return false; } } return true; }; if (ok()) cout << "Yes" << '\n'; else cout << "No" << '\n'; return 0; }
D - Good Tuple Problem (abc327 D)
题目大意
给定两个数组长度为,仅包含的,问它们是否是一对好数组。
若两个数组是一对好数组,则存在一个长度为的数组 ,使得 。
解题思路
将条件抽象成图,相当于是点和点连一条边,它们的颜色不能相同。
即最后是否是一张二分图,黑白染色即可。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, m; cin >> n >> m; vector<int> a(m), b(m); for (auto& i : a) { cin >> i; --i; } for (auto& i : b) { cin >> i; --i; } vector<vector<int>> edge(n); for (int i = 0; i < m; ++i) { edge[a[i]].push_back(b[i]); edge[b[i]].push_back(a[i]); } vector<int> col(n, -1); bool ok = true; auto BFS = [&](int st) { queue<int> team; team.push(st); col[st] = 0; while (!team.empty()) { int u = team.front(); team.pop(); int target = (col[u] ^ 1); for (auto& v : edge[u]) { if (col[v] == -1) { col[v] = target; team.push(v); } else if (col[v] != target) { ok = false; return; } } } }; for (int i = 0; i < n && ok; ++i) { if (col[i] == -1) BFS(i); } if (ok) cout << "Yes" << '\n'; else cout << "No" << '\n'; return 0; }
E - Maximize Rating (abc327 E)
题目大意
给定场比赛的表现分,现在选择一些场比赛,使得这些场比赛的 最大。
如果选择了 场比赛 ,则 为
注意这里的要按照原来的顺序排列。
解题思路
枚举,有几项就变成常数,唯一的变数就是最大化,这其实就是个经典的背包问题。
设表示前 场比赛,我选择了场的的最大值。
转移就是考虑当前比赛选或不选,则
初始条件就是。
答案就是
时间复杂度是
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const double inf = numeric_limits<double>::max(); int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; cin >> n; vector<double> dp(n + 1, -inf); dp[0] = 0; for (int i = 0; i < n; ++i) { vector<double> dp2 = dp; int p; cin >> p; for (int j = 1; j <= i + 1; ++j) { dp2[j] = max(dp2[j], dp[j - 1] * 0.9 + p); } dp2.swap(dp); } double ans = -inf; double bottom = 1; for (int i = 1; i <= n; ++i) { ans = max(ans, dp[i] / bottom - 1200 / sqrt(i)); bottom = bottom * 0.9 + 1; } cout << fixed << setprecision(10) << ans << '\n'; return 0; }
F - Apples (abc327 F)
题目大意
二维平面,给定若干个点,问一个长宽为的矩形所能覆盖的点的数量的最大值。
解题思路
枚举一个维度(时间),问题就是在的范围内的另一维度(地点)的宽度为的点数的最大值。
设数组表示 ,即当前时间范围内的一个区间的点数,随着时间的流逝,会有新的点加入,会有旧的点删去。其影响的都是一个长度为 的区间,用线段树维护这个数组 即可。
时间复杂度为
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const int N = 2e5 + 8; class segtree { #define lson (root << 1) #define rson (root << 1 | 1) int max[N << 2], lazy[N << 2]; inline void pushdown(int root) { if (lazy[root]) { lazy[lson] += lazy[root]; max[lson] += lazy[root]; lazy[rson] += lazy[root]; max[rson] += lazy[root]; lazy[root] = 0; } } void update(int root, int l, int r, int L, int R, LL val) { if (L <= l && r <= R) { lazy[root] += val; max[root] += val; return; } pushdown(root); int mid = (l + r) >> 1; if (L <= mid) update(lson, l, mid, L, R, val); if (R > mid) update(rson, mid + 1, r, L, R, val); max[root] = std::max(max[lson], max[rson]); } int query(int root, int l, int r) { return max[root]; } public: int n; void update(int L, int R, LL val) { update(1, 1, n, L, R, val); } int query() { return query(1, 1, n); } }; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, d, w; cin >> n >> d >> w; vector<array<int, 2>> apple(n); for (auto& i : apple) cin >> i[0] >> i[1]; vector<int> id(n); iota(id.begin(), id.end(), 0); ranges::sort(id, [&](int a, int b) { return apple[a][0] < apple[b][0]; }); segtree seg; seg.n = N - 8; auto it = id.begin(); int ans = 0; for (auto& i : id) { while (it != id.end() && apple[*it][0] + d <= apple[i][0]) { seg.update(max(1, apple[*it][1] - w + 1), apple[*it][1], -1); it = next(it); } seg.update(max(1, apple[i][1] - w + 1), apple[i][1], 1); ans = max(ans, seg.query()); } cout << ans << '\n'; return 0; }
G - Many Good Tuple Problems (abc327 G)
题目大意
若两个数组是一对好数组,则存在一个长度为的数组 ,使得 。
问所有的长度为,仅包含 的共 对数组中,好数组的数量。
解题思路
注意到一对数组是好数组,我们将每一位的数字连一条边,这意味着它们所形成的图是一张二分图。
考虑对这个二分图计数。
首先这个二分图会有重边,我们将重边都看成一条边,即设表示 个点 条边的简单二分图的方案数。
它跟答案之间差了个常数——将条相互区分的边分配到这 个相互区分的边集合,且每个集合不为空的方案数。
注意到第二类斯特林数表示将 个相互区分的球放到 个互不区分的盒子的方案数, 则有。不过其实它可以通过二项式反演得到:设意义类似,但集合可以为空,有,根据二项式反演即得
这样我们可以在求出 。
注意到一条边,我们可以让 ,也可以 ,因此有两种构造数组的方式,所有还有个 的系数。
由此 ,其中表示可能的最大的边数,显然是左右点数均分,为
以下考虑如何求。
注意到二分图是可以将点分成左右两部分,由此可以枚举左边部分点(白色点)的数量,注意到点标号不影响结果,因此方案数有 。
然后考虑连边情况,每一条边都是从左边个点选一个点,右边 个点选一个点,然后连边,因此方案数是。
但思考会发现上述结果并不是,注意到一个比较显然的情况,连边并不保证二分图联通,可能会有单独的一个点。在上述方案计算中,我们会分别考虑该点处于左边(染成了白色)和右边的情况,而在 表示的情况中,应当只考虑一次。进一步的,对于一个有个连通块的合法二分图,它对于 的贡献是 ,但上述结果计算得来的贡献是 ,因为每个二分图的染色方案有两种。
我们记上述的结果为 ,即 个点条边的二分图的染色的方案数。注意到上述算重的因素是连通块个数,但从中难以知晓连通块个数的信息,难以从 中得到 。
怎么办呢,注意到关键还是连通块个数的问题,转换需要一个桥梁。我们设表示 个点 条边的连通的二分图的染色的方案数。即连通块数为的方案数。
和 的区别仅仅是连通块个数的问题,可以通过容斥得到。
考虑从减去不合法的情况,即 号点(其实是标号最小的点,号点方便理解)的连通块个数不是 的情况,通过枚举其点数和边数以及点的标号情况,有
是一个连通块的染色方案数,那二分图的方案数就是 。即设 表示 个点 条边的连通的二分图的方案数。
和 的区别仅仅是连通块个数的问题,同 和 的关系,可以通过容斥得到 。
同样考虑枚举号点所在连通块的点数、边数和点的标号的情况,有
由此得到,就能得到答案了。
综上,我们设
- 表示二分图的方案数
- 表示连通二分图的方案数
- 表示二分图染色的方案数
- 表示连通二分图染色的方案数
然后通过枚举染白色的点数量很轻易地求出,由于染色和二分图之间存在关于连通块个数的关系, 需要一个连通(连通块个数为)作为桥梁,通过 。
注意初值条件。
最后再通过一个组合数得到
其中表示将个相互区分的球放到 个相互区分的盒子,且每个盒子非空的方案数。
总的时间复杂度是
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; #define FOR(i, x, y) \ for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i) #define FORD(i, x, y) \ for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i) const int mo = 998244353; const int M = 1e3; LL bin(LL x, LL n, LL MOD) { LL ret = MOD != 1; for (x %= MOD; n; n >>= 1, x = x * x % MOD) if (n & 1) ret = ret * x % MOD; return ret; } inline LL get_inv(LL x, LL p) { return bin(x, p - 2, p); } LL invf[M], fac[M] = {1}; void fac_inv_init(LL n, LL p) { FOR(i, 1, n) fac[i] = i * fac[i - 1] % p; invf[n - 1] = bin(fac[n - 1], p - 2, p); FORD(i, n - 2, -1) invf[i] = invf[i + 1] * (i + 1) % p; } inline LL C(LL n, LL m) { // n >= m >= 0 return n < m || m < 0 ? 0 : fac[n] * invf[m] % mo * invf[n - m] % mo; } int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); fac_inv_init(M, mo); int n, m; cin >> n >> m; int maxL = n / 2 * (n + 1) / 2; vector<vector<int>> h(n + 1, vector<int>(maxL + 1, 0)); for (int i = 1; i <= n; ++i) for (int j = 0; j <= maxL; ++j) for (int k = 0; k <= i; ++k) { h[i][j] += C(i, k) * C(k * (i - k), j) % mo; if (h[i][j] >= mo) h[i][j] -= mo; } vector<vector<int>> g(n + 1, vector<int>(maxL + 1, 0)); for (int i = 1; i <= n; ++i) for (int j = 0; j <= maxL; ++j) { g[i][j] = h[i][j]; for (int k = 1; k <= i; ++k) for (int l = 0; l <= j; ++l) { g[i][j] -= C(i - 1, k - 1) * g[k][l] % mo * h[i - k][j - l] % mo; if (g[i][j] < 0) g[i][j] += mo; } } vector<vector<int>> a(n + 1, vector<int>(maxL + 1, 0)); int inv2 = get_inv(2, mo); for (int i = 1; i <= n; ++i) for (int j = 0; j <= maxL; ++j) { a[i][j] = 1ll * g[i][j] * inv2 % mo; } vector<vector<int>> f(n + 1, vector<int>(maxL + 1, 0)); f[0][0] = 1; for (int i = 1; i <= n; ++i) for (int j = 0; j <= maxL; ++j) for (int k = 1; k <= i; ++k) for (int l = 0; l <= j; ++l) { f[i][j] += C(i - 1, k - 1) * a[k][l] % mo * f[i - k][j - l] % mo; if (f[i][j] >= mo) f[i][j] -= mo; } vector<int> mm(maxL + 1); for (int i = 0; i <= maxL; ++i) mm[i] = bin(i, m, mo); vector<int> x(maxL + 1); for (int i = 0; i <= maxL; ++i) { int sig = 1 - 2 * (i & 1); for (int j = 0; j <= i; ++j) { x[i] = (x[i] + C(i, j) * mm[j] % mo * sig) % mo; if (x[i] < 0) x[i] += mo; sig *= -1; } } int ans = 0; int p2 = bin(2, m, mo); for (int i = 1; i <= maxL; ++i) { ans += 1ll * p2 * f[n][i] % mo * x[i] % mo; if (ans >= mo) ans -= mo; } cout << ans << '\n'; return 0; }
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/17809838.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步