AtCoder Beginner Contest 287
A - Majority (abc287 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; cin >> n; int ans = 0; while(n--){ string s; cin >> s; ans += (s[0] == 'F') * 2 - 1; } if (ans > 0) cout << "Yes" << '\n'; else cout << "No" << '\n';; return 0; }
B - Postal Card (abc287 b)
题目大意
给定个字符串和 个模板。
问有多少个字符串的后缀包含在这 个模板内。
解题思路
用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, m; cin >> n >> m; vector<string> s(n); for(auto &i : s) cin >> i; set<string> f; for(int i = 0; i < m; ++ i){ string s; cin >> s; f.insert(s); } int ans = count_if(s.begin(), s.end(), [&](string& a){ return f.count(a.substr(3)) > 0; }); cout << ans << '\n'; return 0; }
C - Path Graph? (abc287 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); int n, m; cin >> n >> m; vector<int> du(n, 0); vector<int> fa(n, 0); iota(fa.begin(), fa.end(), 0); function<int(int)> findfa = [&](int x){ return fa[x] == x ? x : fa[x] = findfa(fa[x]); }; for(int i = 0; i < m; ++ i){ int u, v; cin >> u >> v; -- u; -- v; du[u] ++; du[v] ++; int fu = findfa(u); int fv = findfa(v); if (fu != fv) fa[fu] = fv; } int one = count(du.begin(), du.end(), 1); int two = count(du.begin(), du.end(), 2); int block = 0; for(int i = 0; i < n; ++ i) block += (fa[i] == i); if (one == 2 && two == n - 2 && block == 1) cout << "Yes" << '\n'; else cout << "No" << '\n'; return 0; }
D - Match or Not (abc287 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); string s, t; cin >> s >> t; int n = s.size(); int m = t.size(); unordered_set<int> bad; int tp = 0; for(int i = n - m; i < n; ++ i, ++ tp){ if (s[i] != '?' && t[tp] != '?' && s[i] != t[tp]) bad.insert(tp); } if (bad.empty()) cout << "Yes" << '\n'; else cout << "No" << '\n'; for(int i = 0; i < m; ++ i){ bad.erase(i); if (s[i] != '?' && t[i] != '?' && s[i] != t[i]) bad.insert(tp); if (bad.empty()) cout << "Yes" << '\n'; else cout << "No" << '\n'; } return 0; }
E - Karuta (abc287 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; cin >> n; vector<string> s(n); for(auto &i : s) cin >> i; vector<int> id(n); iota(id.begin(), id.end(), 0); sort(id.begin(), id.end(), [&](int x, int y){ return s[x] < s[y]; }); vector<int> ans(n); auto LCP = [&](string& l, string& r){ int tmp = 0; while(tmp < l.size() && tmp < r.size() && l[tmp] == r[tmp]) ++ tmp; return tmp; }; for(int i = 0; i < n - 1; ++ i){ int l = id[i], r = id[i + 1]; int tmp = LCP(s[l], s[r]); ans[l] = max(ans[l], tmp); ans[r] = max(ans[r], tmp); } for(auto &i : ans) cout << i << '\n'; return 0; }
貌似还可以建一棵Trie
树来求解。
F - Components (abc287 f)
题目大意
给定一棵有个点的树,在所有的非空点集中,回答下列问题:
对于 ,有多少个点集所形成的连通块个数恰好是。
数量对 取模。
解题思路
当设表示以 为根的子树,能形成连通块数量恰好为 的,点集数时,会发现在合并子树的时候,如果点和子树节点都选择的时候,连通块个数会减一,其余情况都不会,因此还要加上是否选择
点 的状态。
设表示以 为根的子树,能形成连通块数量恰好为 的,且不选择
/选择
点 的点集数。
合并时枚举儿子树的连通块大小以及点和儿子的选择情况。
咋一看这样的复杂度会是,但如果每次枚举的范围都是儿子树的大小,可以证明这样的树型 的复杂度是 的。
但还是一直T把第三维的长度为的 vector
换成了array
就过了(?
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const int mo = 998244353; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; cin >> n; vector<vector<int>> edge(n); for(int i = 1; i < n; ++ i){ int u, v; cin >> u >> v; -- u; -- v; edge[u].push_back(v); edge[v].push_back(u); } vector<vector<array<int, 2>>> dp(n, vector<array<int, 2>>(n + 1, array<int, 2>{0, 0})); function<int(int, int)> dfs = [&](int u, int fa){ int sz = 1; dp[u][1][1] = 1; dp[u][0][0] = 1; for(auto &v : edge[u]){ if (v == fa) continue; int sonsz = dfs(v, u); vector<array<int, 2>> tmp(n + 1, array<int, 2>{0, 0}); for(int i = 0; i <= sz; ++ i){ for(int j = 0; j <= sonsz; ++ j){ tmp[i + j][1] = (tmp[i + j][1] + 1ll * dp[u][i][1] * dp[v][j][0] % mo) % mo; if (i + j - 1 >= 0) tmp[i + j - 1][1] = (tmp[i + j - 1][1] + 1ll * dp[u][i][1] * dp[v][j][1] % mo) % mo; tmp[i + j][0] = (tmp[i + j][0] + 1ll * dp[u][i][0] * (dp[v][j][1] + dp[v][j][0]) % mo) % mo; } } dp[u].swap(tmp); sz += sonsz; } return sz; }; dfs(0, 0); for(int i = 1; i <= n; ++ i) cout << (dp[0][i][0] + dp[0][i][1]) % mo << '\n'; return 0; }
G - Balance Update Query (abc287 g)
题目大意
有种类型的卡,每种卡有张。每种卡有分数
和大小
属性。
维护以下三种操作:
1 x y
,将第种卡的分数
修改为。2 x y
,将第种卡的大小
修改为。3 x
,选张卡,要求最大化分数和,且每种类型的卡的数量不得超过其大小
。
解题思路
不考虑修改,仅考虑询问的话,很显然我们从分数
大的卡贪心取,直到取了张。由于张数随着种类的增加而越来越多,因此可以二分取的卡的种类,计算一下取的卡的数量(其实就是大小
属性的前缀和)求得答案。
而如果加上操作 ,实际上是要维护下大小
属性的前缀和,用线段树维护即可(线段树二分的话复杂度还是不变的)
但如果加上操作的话,就要动态维护卡的顺序就寄了,后续再思索思索我傻了,可以事先给它预留个位置,大小
为。
即事先对所有可能出现的分数
从大到小排序,对那些还没出现的分数
的大小
置为 。然后维护大小
的前缀和以及分数
大小
的前缀和。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const int N = 4e5 + 8; class Segment{ #define lson (root << 1) #define rson (root << 1 | 1) LL cnt[N << 2], sum[N << 2]; public: void add(int root, int l, int r, int pos, LL val, LL a){ if (l == r){ cnt[root] += val; sum[root] += val * a; return; } int mid = (l + r) >> 1; if (pos <= mid) add(lson, l, mid, pos, val, a); else add(rson, mid + 1, r, pos, val, a); cnt[root] = cnt[lson] + cnt[rson]; sum[root] = sum[lson] + sum[rson]; } struct Res{ int pos; LL cnt; LL sum; }; Res query(int root, int l, int r, int k){ if (l == r){ if (cnt[root] <= k){ return {r, cnt[root], sum[root]}; }else{ return {r - 1, 0, 0}; } } int mid = (l + r) >> 1; if (cnt[lson] <= k){ auto res = query(rson, mid + 1, r, k - cnt[lson]); res.cnt += cnt[lson]; res.sum += sum[lson]; return res; }else{ return query(lson, l, mid, k); } } }seg; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; cin >> n; vector<int> tmp, a(n), b(n); for(int i = 0; i < n; ++ i){ cin >> a[i] >> b[i]; tmp.push_back(a[i]); } int q; cin >> q; vector<array<int, 3>> op(q); for(auto &[o, x, y] : op){ cin >> o >> x; if (o == 3) y = 0; else { -- x; cin >> y; } if (o == 1) tmp.push_back(y); } sort(tmp.begin(), tmp.end(), greater<int>()); auto get_rank = [&](int val){ return lower_bound(tmp.begin(), tmp.end(), val, greater<int>()) - tmp.begin() + 1; }; for(int i = 0; i < n; ++ i){ int pos = get_rank(a[i]); seg.add(1, 1, tmp.size(), pos, b[i], a[i]); } for(auto &[o, x, y] : op){ if (o == 1){ int pos = get_rank(a[x]); seg.add(1, 1, tmp.size(), pos, -b[x], a[x]); a[x] = y; pos = get_rank(a[x]); seg.add(1, 1, tmp.size(), pos, b[x], a[x]); }else if (o == 2){ int pos = get_rank(a[x]); seg.add(1, 1, tmp.size(), pos, -b[x], a[x]); b[x] = y; seg.add(1, 1, tmp.size(), pos, b[x], a[x]); }else{ auto [pos, cnt, sum] = seg.query(1, 1, tmp.size(), x); if (pos == tmp.size() && cnt < x) cout << -1 << '\n'; else{ cout << sum + (x - cnt) * tmp[pos] << '\n'; } } } return 0; }
Ex - Directed Graph and Query (abc287 h)
题目大意
给定一张有向图,回答组询问。
每组询问包括 ,问从 到点 的路径的代价最小值。
一条路径的代价是其经过的点的编号的最大值。
解题思路
如果是无向图,可以对原图跑一棵最小生成树,边权就是两点的点编号较大的那个。
然后每次询问其最大的,连接了这两个点的点,就是Kruscal
重构树中两点的LCA
。
但这是有向图再思索思索,有向图的问题就是最小生成树的生成办法不太一样。但其本质的思想还是从编号小的点开始考虑。
注意到floyd
算法中的第一层循环的含义就是仅考虑号点时,各个点之间的距离,这个距离
可以换成连通性
,这样可以用 bitset
优化运算,复杂度是。以及 只有 ,那就每更新一次 后更新一下所有询问的答案即可。时间复杂度是
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const int N = 2e3 + 1; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, m; cin >> n >> m; vector<bitset<N>> ok(n); for(int i = 0; i < m; ++ i){ int u, v; cin >> u >> v; -- u; -- v; ok[u][v] = 1; } for(int i = 0; i < n; ++ i) ok[i][i] = 1; int q; cin >> q; vector<int> ans(q); vector<array<int, 2>> query(q); for(auto &[u, v] : query){ cin >> u >> v; -- u; -- v; } for(int k = 0; k < n; ++ k){ for(int i = 0; i < n; ++ i){ if (ok[i][k]) ok[i] |= ok[k]; } for(int i = 0; i < q; ++ i){ auto &[u, v] = query[i]; if (ans[i] == 0 && ok[u][v]) ans[i] = k; } } for(int i = 0; i < q; ++ i){ auto &[u, v] = query[i]; if (ans[i] == 0) cout << -1 << '\n'; else cout << max({u, v, ans[i]}) + 1 << '\n'; } return 0; }
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/17071525.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步