AtCoder Beginner Contest 282
A - Generalized ABC (abc282 a)
题目大意
给定,输出一个字符串,从'A','B','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; cin >> n; string s; for(int i = 0; i < n; ++ i) s += 'A' + i; cout << s << '\n'; return 0; }
B - Let's Get a Perfect Score (abc282 b)
题目大意
给定一个矩阵。选两行,其每一列至少有一个 。问满足要求的选法。
解题思路
范围不大,暴力即可。
神奇的代码
#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) int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, m; cin >> n >> m; vector<string> qwq(n); for(auto &i : qwq) cin >> i; int ans = 0; auto check = [&](int x, int y){ int cnt = 0; FOR(i, 0, m){ cnt += (qwq[x][i] == 'o' || qwq[y][i] == 'o'); } return cnt == m; }; FOR(i, 0, n) FOR(j, i + 1, n){ ans += check(i, j); } cout << ans << '\n'; return 0; }
C - String Delimiter (abc282 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; string s; cin >> n >> s; bool ok = false; for(auto &i : s){ if (i == '\"') ok ^= 1; else if (i == ',' && !ok) i = '.'; } cout << s << '\n'; return 0; }
D - Make Bipartite 2 (abc282 d)
题目大意
给定一张个点的无向图,给其加一条边,满足其是二分图。问满足该条件的方案数。
解题思路
注意原图不一定连通。
对原图进行黑白染色,如果颜色冲突则答案为。
否则,对于一个连通块内,假设点数为,边数为 ,染了个白点和 个黑点,则满足要求的方案数为 。该连通块还能连向其他连通块,且可以任意连,其方案数为。
对所有连通块累计方案数即为答案。
神奇的代码
#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) int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, m; cin >> n >> m; vector<vector<int>> edge(n + 1); vector<int> deep(n + 1, -1); FOR(i, 0, m){ int u, v; cin >> u >> v; edge[u].push_back(v); edge[v].push_back(u); } LL ans = 0; vector<int> col(2, 0); bool ok = true; int cnt = 0; set<pair<int, int>> ee; function<void(int, int, int)> dfs = [&](int u, int fa, int cc){ deep[u] = cc; col[cc] ++; ++ cnt; for(auto &v : edge[u]){ if (v == fa) continue; ee.insert({u, v}); ee.insert({v, u}); if (deep[v] != -1){ if (deep[v] == deep[u]) ok = false; }else dfs(v, u, cc ^ 1); } }; int block = 0; FOR(i, 1, n + 1){ if (deep[i] == -1){ ++ block; cnt = 0; col[0] = col[1] = 0; ee.clear(); dfs(i, i, 0); int cntm = ee.size() / 2; ans += 1ll * col[0] * col[1] - cntm; ans += 1ll * cnt * (n - cnt); n -= cnt; } } if (!ok) ans = 0; cout << ans << '\n'; return 0; }
E - Choose Two and Eat One (abc282 e)
题目大意
给定个数,每个数范围,每次选择两个数,获得 分数。再将其中一个数丢弃,直至剩一个数。问获得的分数最大值。
解题思路
对于每个数,可能被选择多次,其中仅一次会被干掉,其他的都能保留。一个与多个的关系与树节点的一个父亲和多个儿子非常类似。
给定一棵树,题意操作相当于每次将叶子节点去掉,同时获得叶子与父亲的边权值。
对原图跑一边最大生成树即可。
神奇的代码
#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) int n, m; LL qpower(LL a, LL b){ LL qwq = 1; while(b){ if (b & 1) qwq = qwq * a % m; a = a * a % m; b >>= 1; } return qwq; } LL calc(LL x, LL y){ return (qpower(x, y) + qpower(y, x)) % m; } int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n >> m; vector<vector<LL>> dis(n + 1, vector<LL>(n + 1, 0)); vector<LL> a(n + 1); for(int i = 1; i <= n; ++ i){ cin >> a[i]; } vector<pair<LL, pair<int, int>>> edge; for(int i = 1; i <= n; ++ i) for(int j = 1; j <= n; ++ j) edge.push_back({calc(a[i], a[j]), {i, j}}); vector<int> fa(n + 1); function<int(int)> findfa = [&](int x){ return fa[x] == x ? x : fa[x] = findfa(fa[x]); }; iota(fa.begin(), fa.end(), 0); sort(edge.begin(), edge.end(), [&](const pair<LL, pair<int, int>>& a, const pair<LL, pair<int, int>>& b){ return a.first > b.first; }); LL ans = 0; for(auto &i : edge){ int fu = findfa(i.second.first); int fv = findfa(i.second.second); if (fu != fv){ fa[fu] = fv; ans += i.first; } } cout << ans << '\n'; return 0; }
F - Union of Two Sets (abc282 f)
题目大意
交互题。给定,要求生成 个区间,并用这些区间回答 组询问。
每组询问给定 ,要求从 个区间选出 个区间,其并集为区间 。
解题思路
其实里的表的做法就能达到题目所述的要求。
即为每个左端点生成长度为的幂的区间。
设长度 ,其最大的小于等于的 的幂为 。
则选择的区间为
神奇的代码
#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) inline int highbit(int x) { return 31 - __builtin_clz(x); } int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; cin >> n; map<pair<int, int>, int> area; int m = 0; for(int i = 1; i <= n; ++ i){ for(int j = 1; i + j - 1 <= n; j <<= 1){ ++ m; area[{i, i + j - 1}] = m; } } cout << area.size() << '\n'; for(auto &i : area){ cout << i.first.first << ' ' << i.first.second << '\n'; } cout.flush(); int q; cin >> q; while(q--){ int l, r; cin >> l >> r; int t = highbit(r - l + 1); cout << area[{l, l + (1 << t) - 1}] << ' ' << area[{r - (1 << t) + 1, r}] << endl; } return 0; }
G - Similar Permutation (abc282 g)
题目大意
定义两个排列的相似度为,差分数组下符号相同的标号数量。
给定,问 长度为 的相似度为 的排列对数,对取模。
解题思路
起初想的是从小到大插入每个数统计方案,但是一旦插入到前面的位置,就会影响到后面的相似度判断,需要记录前面的升降趋势状态数会非常大,该方向不可行。
然后考虑按一位一位填数。要知道该位填的数与上一位关系的大小关系,需要记录上一位数是多少,但这样的话也得记录目前已经填了哪些数,这样才能知道有多少种填法是小于上一个数。而已经填了哪些数这一状态是指数级的。局面一度陷入僵局。
细细一想,我们需要的状态:上一个数填的是什么,以及当前填了哪些数,是为了决定最后两个数字的大小关系(这关系到相似度是否),而并不关心这两个数具体是多少,我们只想知道,有多少种填法是小于,有多少种填法是大于。
因此我们可以设计的状态是:当前已填的最后一个数在未填的数中排名第几位。通过这一状态,我们就知道:有多少种填法是小于,有多少种填法是大于。
即表示我们填了 个数,两个排列的相似度为 , 第一个排列的最后一个数在未填数中排名为,第二个排列的最后一个数在未填数中排名为 的方案数。
则
即新填一个数
- 相似度不增加,则是一小一大和一大一小两种情况
- 相似度加一,则是一小一小和一大一大两种情况
朴素转移是,可以用二维前缀和优化成 ,因此总的时间复杂度为
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const int N = 100 + 8; int n, k, p, cur; int dp[2][N][N][N], sum[N][N]; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n >> k >> p; cur = 0; for(int i = 1; i <= n; ++ i) for(int j = 1; j <= n; ++ j) dp[cur][0][i][j] = 1; for(int i = 2; i <= n; ++ i){ cur ^= 1; memset(dp[cur], 0, sizeof(dp[cur])); for(int j = 0; j <= k; ++ j){ int up = n - i + 2; // 未填数数量+1,即排名的最大值 for(int a = 1; a <= up; ++ a) for(int b = 1; b <= up; ++ b){ sum[a][b] = (0ll + sum[a - 1][b] + sum[a][b - 1] - sum[a - 1][b - 1] + dp[cur ^ 1][j][a][b]) % p; if (sum[a][b] < 0) sum[a][b] += p; } for(int a = 1; a <= up; ++ a) for(int b = 1; b <= up; ++ b){ dp[cur][j][a][b] = (0ll + dp[cur][j][a][b] + sum[a][up] - sum[a][b] + sum[up][b] - sum[a][b]) % p; if (dp[cur][j][a][b] < 0) dp[cur][j][a][b] += p; dp[cur][j + 1][a][b] = (0ll + dp[cur][j + 1][a][b] + sum[a][b] + sum[up][up] - sum[a][up] - sum[up][b] + sum[a][b]) % p; if (dp[cur][j + 1][a][b] < 0) dp[cur][j + 1][a][b] += p; } } } cout << dp[cur][k][1][1] << '\n'; return 0; }
Ex - Min + Sum (abc282 h)
题目大意
给定两个长度为的数组和一个整数。问有多少对 ,满足
解题思路
条件有两项,我们先考虑第一项。
考虑作为其区间的最小值,那么该区间 需要满足一定条件。
用单调栈预处理 数组分别表示 左边第一个(最靠近的)小于等于 的下标,以及右边第一个小于 的下标, 那么满足的区间 的最小值都是 ,这样就确定好了第一项。
考虑和式,首先通过前缀和将和式变成两数相减的形式。因为前缀和是单调递增的,如果我们枚举,通过二分可以找到符合条件的 ,同理枚举 ,通过二分也能找到符合条件的 。
看和 的枚举个数(即和 的大小),取较小的那边枚举二分求解即可。咋一看这做法的复杂度可能有的量级,但可以证明这样的复杂度其实是
考虑全局最小值,其枚举范围是整个数组。考虑完后该最小值将整个数组切分成两部分,然后递归考虑左边的最小值和右边的最小值。这里有分治的想法,这里我们主要关注合并。
考察每个端点被枚举的次数。因为它所在的区间是长度较小的,当枚举完后,下一个枚举到它的区间的长度(左右合并成一个大区间)至少翻倍,最多翻倍次就到长度为 了。所以每个端点被枚举的次数是 次,加上每次枚举的二分有一个。因此总的时间复杂度为
神奇的代码
#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; LL s; cin >> n >> s; vector<LL> a(n + 1), b(n + 1); for(int i = 1; i <= n; ++ i) cin >> a[i]; for(int i = 1; i <= n; ++ i) cin >> b[i]; vector<LL> sumb(n + 1); partial_sum(b.begin() + 1, b.end(), sumb.begin() + 1); vector<int> l(n + 1), r(n + 1); stack<int> pos; for(int i = 1; i <= n; ++ i){ while(!pos.empty() && a[pos.top()] >= a[i]){ r[pos.top()] = i - 1; pos.pop(); } pos.push(i); } while(!pos.empty()){ r[pos.top()] = n; pos.pop(); } for(int i = n; i >= 1; -- i){ while(!pos.empty() && a[pos.top()] > a[i]){ l[pos.top()] = i + 1; pos.pop(); } pos.push(i); } while(!pos.empty()){ l[pos.top()] = 1; pos.pop(); } auto solve = [&](int l, int mid, int r){ LL cnt = 0; int len1 = mid - l + 1; int len2 = r - mid + 1; vector<LL>::iterator sb = sumb.begin(), st = sb + l - 1, midd = sb + mid, en = sb + r + 1; LL up = s - a[mid]; if (len1 < len2){ for(auto it = st; it != midd; it = next(it)){ auto pos = upper_bound(midd, en, up + *it); cnt += pos - midd; } }else{ for(auto it = midd; it != en; it = next(it)){ auto pos = lower_bound(st, midd, *it - up); cnt += midd - pos; } } return cnt; }; LL ans = 0; for(int i = 1; i <= n; ++ i){ ans += solve(l[i], i, r[i]); } cout << ans << '\n'; return 0; }
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/16990328.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】