AtCoder Beginner Contest 372

省流版
  • A. 暴力即可
  • B. 转换3进制即可
  • C. 考虑答案的组成,仅修改发生变化的部分即可
  • D. 维护答案数组ansi,考虑枚举 j对哪些 i有贡献,通过单调栈找到对应的区间i ,通过差分维护区间加法即可
  • E. 并查集维护连通块,set维护点标号大小,合并set时启发式合并,查询则从对应连通块的set最大值往前找k次即可
  • F. 考虑朴素dp,发现有效转移只有 O(mk) 个,记忆化搜索即可

A - delete . (abc372 A)

题目大意

给定一个字符串,删除其中的.

解题思路

依次判断每个字符,如果不是.就输出。

python可以一行,

神奇的代码
print(input().replace('.', ''))


B - 3^A (abc372 B)

题目大意

给定一个m,将其表达成若干个 3的幂的和。

解题思路

其实就是将m转换成三进制,看三进制下每一个幂的系数。

进制转换一下即可。

神奇的代码
#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 m;
cin >> m;
vector<int> ans;
for (int i = 0; i <= 10; ++i) {
int cnt = m % 3;
while (cnt--) {
ans.push_back(i);
}
m /= 3;
}
cout << ans.size() << '\n';
for (auto i : ans)
cout << i << " ";
cout << '\n';
return 0;
}


C - Count ABC Again (abc372 C)

题目大意

给定一个字符串s,进行以下q次操作。

每次操作,将sx=c。问操作完后,字符串 ABCs的出现次数。

解题思路

f(i)=1/0表示 sisi+1si+2是/不是 ABC

答案就是i=1nf(i)

每次修改,其实只有 3f(i)的值可能发生变化。

因此先计算出 i=1nf(i),然后对于每次修改,先减去f(x2)+f(x1)+f(x)个 ,然后修改字符后,重新计算f(x2)+f(x1)+f(x)即可得到新的答案,而不需要重新计算 i=1nf(i)

时间复杂度就为O(q)

神奇的代码
#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, q;
string s;
cin >> n >> q >> s;
int ans = 0;
auto in_range = [&](int i) { return i >= 0 && i < n; };
auto check = [&](int x) {
for (int i = 0; i < 3; ++i)
if (!in_range(x + i) || s[x + i] != 'A' + i)
return false;
return true;
};
for (int i = 0; i < n; ++i) {
ans += check(i);
}
while (q--) {
int x;
string c;
cin >> x >> c;
--x;
for (int i = x - 2; i <= x; ++i) {
ans -= check(i);
}
s[x] = c[0];
for (int i = x - 2; i <= x; ++i) {
ans += check(i);
}
cout << ans << '\n';
}
return 0;
}


D - Buildings (abc372 D)

题目大意

给定n个数的数组 a,对于每个 i[1,n] ,问j的数量满足 (i,j]aj是最大值。

解题思路

如果枚举i,求 j,复杂度是 O(n2)

反过来,枚举 j,考虑它会对哪些 i1的贡献。 会发现只要满足max(a[i+1..j])j,那么这个 j就会对 i1的贡献。

即对于每个数aj,我们找其左边第一个al>aj,那么ans[l..j1]+=1

对于第一个,如何找到其左边第一个al>aj呢?这是一个经典问题,从右往左,用单调栈维护一个递减的数列即可(栈顶到栈底是递减的)。

对于第二个,有若干次区间加法,但最后只有一次查询,可以用差分数组将区间加变成单点修改,最后还原出答案数组即可。

神奇的代码
#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<int> h(n);
for (auto& x : h)
cin >> x;
stack<int> s;
vector<int> diff(n + 1);
for (int i = n - 1; i >= 0; i--) {
while (!s.empty() && h[s.top()] < h[i]) {
diff[i]++;
diff[s.top()]--;
s.pop();
}
s.push(i);
}
while (!s.empty()) {
diff[0]++;
diff[s.top()]--;
s.pop();
}
int ans = 0;
for (int i = 0; i < n; ++i) {
ans += diff[i];
cout << ans << " \n"[i == n - 1];
}
return 0;
}


E - K-th Largest Connected Components (abc372 E)

题目大意

给定n个点,初始无边。维护两类操作。

  • 1 u v,连一条无向边uv
  • 2 v k,找v所在连通块的第 k大的点的标号。

k10

解题思路

注意到k10,如果能用 set维护一个连通块的所有点的标号,那么直接从 rbegin()暴力数k个就是答案,

如果维护这个 set呢?连通块的信息自然用并查集维护,当合并两个连通块时,其 set也要合并,我们可以将小的 set合并到大的 set中,即启发式合并,每次合并 set最坏的复杂度是 O(n),但最坏情况下,合并后的 set大小会翻倍,最多翻倍 log次就达到 n个点,即最坏情况的出现次数只有 O(logn)次,因此启发式合并的时间复杂度是 O(nlogn)

维护好 set后,直接从 rbegin()k个点即为答案。特判少于k个点的情况。

时间复杂度是 O((n+qk)logn)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
class dsu {
public:
vector<int> p;
vector<int> sz;
vector<set<int>> node;
int n;
dsu(int _n) : n(_n) {
p.resize(n);
sz.resize(n);
node.resize(n);
iota(p.begin(), p.end(), 0);
fill(sz.begin(), sz.end(), 1);
for (int i = 0; i < n; i++) {
node[i].insert(i);
}
}
inline int get(int x) { return (x == p[x] ? x : (p[x] = get(p[x]))); }
inline bool unite(int x, int y) {
x = get(x);
y = get(y);
if (x != y) {
if (sz[x] < sz[y])
swap(x, y);
node[x].insert(node[y].begin(), node[y].end());
p[y] = x;
sz[x] += sz[y];
return true;
}
return false;
}
};
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, q;
cin >> n >> q;
dsu d(n);
while (q--) {
int op;
cin >> op;
if (op == 1) {
int u, v;
cin >> u >> v;
--u, --v;
d.unite(u, v);
} else {
int v, k;
cin >> v >> k;
--v;
int fa = d.get(v);
if (d.node[fa].size() < k) {
cout << -1 << '\n';
} else {
auto it = d.node[fa].rbegin();
advance(it, k - 1);
cout << *it + 1 << '\n';
}
}
}
return 0;
}


F - Teleporting Takahashi 2 (abc372 F)

题目大意

给定一张有向图,它首先是个环,然后在此基础上加了m50条有向边。

问从 1号点出发,走 k个点,所形成的点的路径 (1,v1,v2,...,vk)的情况数。

解题思路

考虑朴素搜索,设dp[u][k]表示从 u号点走 k步的方案数,显然其答案就是 uvdp[v][k1]

但这复杂度是 O(nk),显然过不了。

但注意到有很多点 uvv其实只有一个即 u+1,即只有一种走法,它们的转移显然是没必要的。我们可以只考虑有分叉点的 u。而这有分叉点的 u最多只有 m个。因此如果我们的转移是从 u分叉点,这样的时间复杂度就是 O(mk), 是可以通过的。

记当前点u,然后找到 u后第一个分叉点 x, 其出度>1,那么 dp[u][k]=xydp[y][k],转移后k可以通过简单的运算得到,即kdis(ux)1

有效的u就只有 2m个,对 dp进行记忆化搜索,复杂度即为 O(mk)。因为用map来记忆化会超时,所以只好离散化有效的 u,转成数组来记忆化了。

神奇的代码
#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, m, k;
cin >> n >> m >> k;
vector<vector<int>> edge(n);
for (int i = 0; i < n; i++) {
edge[i].push_back((i + 1) % n);
}
vector<int> tr;
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
u--;
v--;
edge[u].push_back(v);
tr.push_back(u);
}
for (int i = 0; i < n; i++) {
sort(edge[i].begin(), edge[i].end());
edge[i].erase(unique(edge[i].begin(), edge[i].end()), edge[i].end());
}
sort(tr.begin(), tr.end());
tr.erase(unique(tr.begin(), tr.end()), tr.end());
auto calc = [&](int u, int v) -> int {
if (u > v)
v += n;
return v - u;
};
int ans = 0;
if (m == 0) {
ans = 1;
} else {
vector<int> nxt(n);
for (int i = 0; i < n; i++) {
auto it = lower_bound(tr.begin(), tr.end(), i);
if (it == tr.end())
it = tr.begin();
nxt[i] = *it;
}
vector<int> id(n, -1);
int cnt = 0;
auto dfs_pre = [&](auto& dfs, int u) -> void {
if (id[u] != -1)
return;
id[u] = cnt++;
int it = nxt[u];
for (int v : edge[it]) {
dfs(dfs, v);
}
};
dfs_pre(dfs_pre, 0);
vector<vector<int>> dp(k + 1, vector<int>(cnt, -1));
auto dfs = [&](auto& dfs, int u, int k) -> int {
if (k == 0) {
return 1;
}
if (dp[k][id[u]] != -1)
return dp[k][id[u]];
int it = nxt[u];
int sum = 0;
int nxt = (u + 1) % n;
int cost = calc(u, it);
if (cost >= k)
return 1;
for (int v : edge[it]) {
sum += dfs(dfs, v, k - cost - 1);
if (sum >= mo)
sum -= mo;
}
return dp[k][id[u]] = sum;
};
ans = dfs(dfs, 0, k);
}
cout << ans << '\n';
return 0;
}


G - Ax + By < C (abc372 G)

题目大意

给定三个n个数的数组a,b,c,问 (x,y)的数量,满足

  • aix+bi<c,对任意 i[1,n]

多组数据。

解题思路

一眼只会O(nai)

神奇的代码


posted @   ~Lanly~  阅读(628)  评论(6编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示