AtCoder Beginner Contest 310
感觉F又双叒叕写复杂了
A - Order Something Else (abc310 A)
题目大意
点杯咖啡,要元,但可以用一个优惠券,使得咖啡只要 元,但你需要额外购买个商品中(价格为)的一个。
问点杯咖啡的最小价格。
解题思路
考虑直接买还是使用优惠券,使用优惠券的话就选个商品中价格最小的。
两种情况取最小即可。
神奇的代码
#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, p, q; cin >> n >> p >> q; int a = 1e9 + 7; while(n--){ int x; cin >> x; a = min(a, x); } cout << min(p, q + a) << '\n'; return 0; }
B - Strictly Superior (abc310 B)
题目大意
给定个商品的价格 ,以及每个商品的 种功能。
问是否可以找到两个商品,满足:
- 商品的 功能商品 都有
- 或者 商品有额外的功能, 商品没有。
解题思路
范围都不大,直接枚举商品,然后比较那三个条件是否满足即可。
对于条件二,可以再花 的时间比较下功能是否覆盖,也可以用 bitset
,直接作与运算
。
神奇的代码
#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<bitset<100>> f(n); vector<int> p(n); for(int i = 0; i < n; ++ i){ cin >> p[i]; int k; cin >> k; while(k--){ int x; cin >> x; -- x; f[i][x] = 1; } } bool ok = false; for(int i = 0; i < n; ++ i) for(int j = 0; j < n; ++ j){ if (i == j) continue; if (p[i] < p[j]) continue; if ((f[i] & f[j]) != f[i]) continue; if (p[i] > p[j] || f[i] != f[j]) ok = true; } if (ok) cout << "Yes" << '\n'; else cout << "No" << '\n'; return 0; }
C - Reversible (abc310 C)
题目大意
给定个字符串,问字符串的种类数量。
两个字符串是相同种类的 ,当且仅当或 。其中 表示字符串 的反串。
解题思路
去重可以用set
。
对于一个字符串,可以在 set
中看看能否找到或 ,不能找到说明是一个新的种类,放进 set
即可。
答案就是最后的set
的元素数量。
神奇的代码
#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; cin >> n; unordered_set<string> qwq; while(n--){ string s; cin >> s; auto t = s; reverse(t.begin(), t.end()); if (qwq.empty() || (qwq.find(s) == qwq.end() && qwq.find(t) == qwq.end())){ qwq.insert(s); } } cout << qwq.size() << '\n'; return 0; }
D - Peaceful Teams (abc310 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, t, m; cin >> n >> t >> m; vector<vector<int>> rule(n); for(int i = 0; i < m; ++ i){ int x, y; cin >> x >> y; -- x, -- y; rule[x].push_back(y); rule[y].push_back(x); } LL ans = 0; vector<int> a(n, -1); auto ok = [&](int pos, int zu){ for(auto &i : rule[pos]){ if (a[i] == zu) return false; } return true; }; function<void(int, int)> dfs = [&](int zu, int pos){ if (zu > t) return; if (pos == n){ if (zu == t) ans ++; return; } for(int i = 0; i < zu; ++ i){ if (ok(pos, i)){ a[pos] = i; dfs(zu, pos + 1); a[pos] = -1; } } a[pos] = zu; dfs(zu + 1, pos + 1); a[pos] = -1; }; dfs(0, 0); cout << ans << '\n'; return 0; }
E - NAND repeatedly (abc310 E)
题目大意
给定一个长度为的数组 ,定义函数 ,其中是非与运算,即与运算的非。
求
解题思路
虽然题意给了的递归定义式有点吓人,但展开后其实就是上述的表达式。
直接计算是 , 思考下这个结果可以发现有压缩的余地。
考虑什么时候 ,可以发现是或者,即它只取决于上一位情况和当前位。
我们在迭代计算,即枚举右端点时,考虑其对答案的贡献是多少,那就是考虑有多少个左端点满足 。因为每一个为的左端点都对答案有 的贡献。
思考可以发现其数量可以从右端点为的情况快速得到。
我们设 表示前 个数,满足 或 的数量,
根据的取值和非与的运算结果,可以得到从 转移过来的式子。
假设 ,则
假设 ,则
- ,注意这个是 的情况。
那么以为右端点的所有区间的贡献就是。
累计求和就是答案了。
初始情况,貌似就全就好了。
神奇的代码
#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; array<LL, 2> dp{0, 0}; LL sum = 0; for(auto &i : s){ array<LL, 2> dp2{0, 0}; if (i == '0'){ dp2[0] = 1; dp2[1] = dp[0] + dp[1]; }else{ dp2[0] = dp[1]; dp2[1] = dp[0] + 1; } sum += dp2[1]; dp.swap(dp2); } cout << sum << '\n'; return 0; }
F - Make 10 Again (abc310 F)
题目大意
给定个骰子,每个骰子等概率掷出 中的一个数。
问一个局面出现的概率,在这个局面中,可以选择一些骰子的结果,其和为 。
解题思路
算概率考虑两个方向,一种是根据定义来算,即一个概率的结果是若干个前序情况概率的累加。
因为本题的所有局面的出现概率都是相同的(所有的累乘),所以还可以是(其实这个公式是定义式的通分的结果)。而全局面数
就是所有的累乘。因此我们考虑如何计算分子。
但是状态定义非常困难,因为它统计的是这个局面可以选出和为,而不是局面选出和为 的方案之类的,常规的一些状态比如 表示前 个骰子掷出后,选择一些骰子,其和为 的方案数(或概率),事实上都会算重。
样例给出的掷出情况 1 3 2 7
,这个局面会被算多次,在考虑 1 2 7
时考虑了一次这个局面, 3 7
时也考虑了一次这个局面,因此这类的状态设计都是不行的。
然后考虑将这个状态再细分一下,因为和为 的情况数很多,上述状态我们把这些状态都揉在一起了,所以我们可以把这些和的 所有情况(比如,或者 等等)细分出来。
即设 表示前 个骰子,其中掷出结果为 的骰子数有 的情况数。虽然有维,但后续状态比如 ,我们可以让它们的取值只有 ,因为再大的取值对我们来讲是没用的了。
最后我们把可以选出和为的 的情况数加起来,就是符合条件的情况数,再除以总情况数,就是答案。
而判断我有个 , 个 , , 个 ,能否选出一些数和为 ,就是一个简单的背包 表示前个数能否组成和为 。
分析下它的复杂度,状态数有 ,每个情况要判断是否能选一些数和为 的复杂度 ,总的复杂度有 ,非常的危险。
但我们考虑逆向情况(其实当时以为正向状态也有重,就直接考虑逆向了,写题解时发现貌似没有重,但分析了下复杂度会炸)即统计不能选一些数和为10的情况数。此时我们可以发现状态数会比较少。首先式子少了最后一维 ,然后一些维度 比如的取值范围只有 而不是刚才的 (因为取 的话就一定能选个 和为 ,其情况数一定是 ),同理和 也是。
这样状态数就有 ,判断合法的复杂度是 ,总的复杂度是 ,是可以通过的了。
转移就考虑当前骰子的取值(从),更大的取值(除了 )对我们所设的状态没有影响,不过记得加上方案数。
当然维 非常不好写,我们可以进行状态压缩,但由于不同维度的取值范围不一样,不能单纯的二进制压缩之类的,但其实如果了解进制转换的话,会发现进制数的每个维度数量不一定要相同,即可以自定义一个进制,最低位有种取值,即逢 进 ,次低位有种 取值,逢进 。依次类推,正向转换和逆向转换就跟转十进制和十进制转换成其他进制一样的公式就可以了。
初始情况就是
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const int mo = 998244353; long long qpower(long long a, long long b){ long long qwq = 1; while(b){ if (b & 1) qwq = qwq * a % mo; a = a * a % mo; b >>= 1; } return qwq; } long long inv(long long x){ return qpower(x, mo - 2); } int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; cin >> n; vector<int> a(n); for(auto &i : a) cin >> i; constexpr int size = 10 * 5 * 4 * 3 * 2 * 2 * 2 * 2 * 2; array<int, 9> ji{10, 5, 4, 3, 2, 2, 2, 2, 2}; array<int, 9> upp{9, 4, 3, 2, 1, 1, 1, 1, 1}; array<LL, size> dp{1}; LL tot = 1; auto craft = [&](int x){ array<int, 9> cnt; for(int i = 0; i < 9; ++ i){ cnt[i] = x % ji[i]; x /= ji[i]; } return cnt; }; auto invcraft = [&](const array<int, 9>& cnt){ int sum = 0; for(int i = 8; i >= 0; -- i){ sum = sum * ji[i] + cnt[i]; } return sum; }; auto do_ok = [](const array<int, 9>& cnt){ array<int, 11> ok{1}; for(int i = 0; i < 9; ++ i){ array<int, 11> ok2{}; for(int j = 0; j <= cnt[i]; ++ j){ for(int k = 0; k <= 10; ++ k){ if (k + (i + 1) * j <= 10) ok2[k + (i + 1) * j] |= ok[k]; } } ok2.swap(ok); } return !ok[10]; }; auto fit = [&](int x, int y){ -- y; auto cnt = craft(x); cnt[y] ++; if (y == 0 && cnt[y] == 10) return false; if (y == 1 && cnt[y] == 5) return false; if (y == 4 && cnt[y] == 2) return false; return true; }; auto ins = [&](int x, int y){ -- y; auto cnt = craft(x); cnt[y] = min(cnt[y] + 1, upp[y]); return invcraft(cnt); }; for(auto &i : a){ array<LL, size> dp2{}; int up = min(i, 9); int left = max(0, i - 10); for(int i = 0; i < size; ++ i){ for(int j = 1; j <= up; ++ j){ if (fit(i, j)){ int nxt = ins(i, j); dp2[nxt] += dp[i]; if (dp2[nxt] >= mo) dp2[nxt] -= mo; } } dp2[i] += dp[i] * left % mo; if (dp2[i] >= mo) dp2[i] -= mo; } dp.swap(dp2); tot = tot * i % mo; } LL ans = 0; auto ok = [&](int i){ return do_ok(craft(i)); }; int tmp = 0; for(int i = 0; i < size; ++ i){ if (ok(i)){ if (dp[i]){ ++ tmp; } ans += dp[i]; if (ans >= mo) ans -= mo; } } ans = ((tot - ans) % mo + mo) % mo * inv(tot) % mo; cout << ans << '\n'; return 0; }
其实感觉写的非常复杂,尤其是那个进制转换,建议看看jls的是如何在4分钟写完这题的qwq。
G - Takahashi And Pass-The-Ball Game (abc310 G)
题目大意
有个高桥,每个高桥有个球, 每一轮,第个高桥会把所有球传给第个高桥。
等概率取值 ,然后进行轮传球。注意这个高桥的球是同时传递的。
传球结束后,问每个高桥手中的球的期望数。
解题思路
<++>
神奇的代码
Ex - Negative Cost (abc310 Ex)
题目大意
<++>
解题思路
<++>
神奇的代码
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/17557285.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步