2024-11-15 17:32阅读: 149评论: 1推荐: 0

AtCoder Beginner Contest 379

省流版
  • A. 模拟即可
  • B. 贪心,有k个就吃,模拟即可
  • C. 维护已经有棋子的格子,有多个棋子往右推,代价等差数列求和,模拟即可
  • D. 注意到植物高度=当前天-种植天,维护植物的种植天然后二分找对应高度的植物即可
  • E. 考虑最终答案每一个数位的值,然后处理进位即可
  • F. 单调栈处理建筑r能看的建筑数量,然后求 [l+1,r]最大高度,二分找到屏蔽单调栈的楼数量即可
  • G. 维护轮廓线状态dp,仅从有效状态往后dp即可

A - Cyclic (abc379 A)

题目大意

给定三个数字,将其进行两次循环移位并输出。

解题思路

可以用rotate函数,将首字母移动到末尾。

神奇的代码
#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 a;
cin >> a;
rotate(a.begin(), a.begin() + 1, a.end());
cout << a << ' ';
rotate(a.begin(), a.begin() + 1, a.end());
cout << a << '\n';
return 0;
}


B - Strawberries (abc379 B)

题目大意

n个牙齿,如果有连续 k个牙齿是健康的,则可以吃一个草莓,然后这 k个牙齿变坏了。

问最多能吃多少个草莓。

解题思路

从左往右依次考虑每个牙齿,很显然,一旦有连续k个健康的就吃一个草莓,该决策一定不劣。

模拟该策略即可。

神奇的代码
#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, k;
string s;
cin >> n >> k >> s;
int ans = 0;
string good = string(k, 'O');
for (int i = 0; i < n; i++) {
if (s.substr(i, k) == good) {
++ans;
fill(s.begin() + i, s.begin() + i + k, 'X');
}
}
cout << ans << '\n';
return 0;
}


C - Sowing Stones (abc379 C)

题目大意

n个格子,初始有 m个格子有棋子数 ai

现可进行操作,如果第 i个格子有棋子,可以将其一个棋子移动到第 i+1个格子。

问最少进行的操作数,使得每个格子都恰好有一个棋子。

解题思路

首先看棋子总数是不是n,不是则做不到。

然后从左到右考虑每个格子,如果其棋子数 >1,则将多余的部分往右推。

cur为从左数第一个还没棋子的格子(这个还没棋子的格子,不考虑该格子原先已有棋子的情况,仅考虑上述向右推的操作的棋子是否到达了该格子)。

对于该格子有 ai个棋子,就可以把这些推到[cur,cur+ai1]这些格子,使得每个格子都恰好有一个棋子,其代价是一个等差数列的求和。然后 cur=cur+ai,考虑下一个格子的棋子往右推即可。

注意 ai2e9,等差数列时的 l+r可能会超 int范围。

神奇的代码
#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> x(m), a(m);
for (auto& i : x)
cin >> i;
for (auto& i : a)
cin >> i;
if (accumulate(a.begin(), a.end(), 0ll) != n) {
cout << -1 << '\n';
} else {
vector<int> id(m);
iota(id.begin(), id.end(), 0);
sort(id.begin(), id.end(), [&](int i, int j) { return x[i] < x[j]; });
LL ans = 0;
int cur = 1;
auto calc = [&](int l, int r, int x) {
int cnt = r - l + 1;
return (0ll + l + r) * cnt / 2 - 1ll * x * cnt;
};
bool ok = true;
for (auto& i : id) {
if (cur < x[i]) {
ok = false;
break;
}
ans += calc(cur, cur + a[i] - 1, x[i]);
cur += a[i];
}
if (!ok) {
ans = -1;
}
cout << ans << '\n';
}
return 0;
}


D - Home Garden (abc379 D)

题目大意

问题陈述

高桥有 10100 个花盆。最初,他没有种植任何植物。

依次处理 Q 个操作,分三种

  • 1:准备一个空花盆并放入一株植物。此时植物的高度是 0
  • 2 T:等待 T 天,现有植物的高度会增加 T
  • 3 H:收获所有高度至少达到 H 的植株,并输出收获植株的数量。收获的植物会从花盆中移出。

解题思路

植物高度为当前天-种植天,因此我们只需维护每个植物的种植天,对于操作三,假设当天是第x天 ,那只需把所有种植天xH的植物全收割即可。

因为植物的 种植天是不断递增的,因此就用一个数组维护一个递增的种植天,然后再用一个变量la维护,已经被收割植物 的种植天的最大值,每次收割,二分找到xH的位置,其与 la的差值就是本次收割的植株数量。

神奇的代码
#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 q;
cin >> q;
vector<LL> pot;
int l = 0;
LL day = 0;
while (q--) {
int op;
cin >> op;
if (op == 1) {
pot.push_back(day);
} else if (op == 2) {
int t;
cin >> t;
day += t;
} else if (op == 3) {
int h;
cin >> h;
int nxt =
upper_bound(pot.begin() + l, pot.end(), day - h) - pot.begin();
cout << nxt - l << '\n';
l = nxt;
}
}
return 0;
}


E - Sum of All Substrings (abc379 E)

题目大意

给定一个长达1e5的数字字符串s。定义 f(i,j)=s[i..j] 。即子串的数字。

i=1nj=1nf(i,j)

解题思路

f(i,j)=s[i..j]=s[j]+s[j1]×10+s[j2]+...

这样我们可以不考虑每个 f(i,j),而是考虑每个 s[j]在答案的贡献,但是因为数很大,这样考虑得写高精度。

怎么办呢?我们还可以考虑最终答案的每一位的数字是多少,即因子10,102,103的系数分别是多少。

考虑个简单的例子,即

f(1,1)+f(1,2)+f(1,3)+f(2,2)+f(2,3)+f(3,3)=(S1)+(10S1+S2)+(100S1+10S2+S3)+(S2)+(10S2+S3)+(S3)=102(S1)+101(S1+2S2)+100(S1+2S2+3S3)

这样我们就知道个位的数是S1+2S2+3S3,处理好进位后,再考虑十位的数是多少。

更一般的,即Ai=j=1ij×Sj,最终的答案就是i=1N10NiAi。再从低位依次考虑进位的情况即可。

神奇的代码
#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;
LL sum = 0;
for (int i = 0; i < n; ++i)
sum += (i + 1) * (s[i] - '0');
vector<LL> ans;
ans.push_back(0);
for (int i = n - 1; i >= 0; --i) {
ans.back() += sum % 10;
int jin = ans.back() / 10;
ans.back() %= 10;
ans.push_back(sum / 10 + jin);
sum -= (i + 1) * (s[i] - '0');
}
while (ans.back() >= 10) {
int jin = ans.back() / 10;
ans.back() %= 10;
ans.push_back(jin);
}
while (ans.back() == 0)
ans.pop_back();
reverse(ans.begin(), ans.end());
for (auto& i : ans)
cout << i;
cout << '\n';
return 0;
}


F - Buildings 2 (abc379 F)

题目大意

给定n个建筑的高度hi,回答 q个询问。

每个询问给定 l,r,问从 l,l+1,...,r1,r建筑往右看,都能看到的建筑的数量。

如果建筑i能看到建筑 j,则不存在 i<k<j,满足 hk>hj

解题思路

如果单问某个建筑往右看,能看到的建筑物数量,其实就是一个从右往左的单调栈——高的建筑会屏蔽矮的建筑,从而矮的建筑会出栈,使得栈是一个从栈顶到栈低是一个递增的情况。

询问可以离线,因此我们按照r从大到小的顺序考虑每个询问,当前单调栈的结果是从r往右看能看到的建筑物高度,然后考虑[l,..r1]它们的有哪些看不到。

注意到其建筑看到的条件:

  • 如果建筑i能看到建筑 j,则不存在 i<k<j,满足 hk>hj

j不变, 序号i越小,其看到的条件就越苛刻,即 i能看到的建筑, [i+1,j1]一定能看到。反之不能看到的话,却不一定。

但我们要找 [l,r]都能看到的建筑,其实就是 l能能看到的建筑数量,按照单调栈的做法,我们即找到[l+1,r]中最高的建筑H,它会屏蔽单调栈里高度小于 H的建筑。

无修改的区间找最大值可以用 ST表,然后找出屏蔽建筑物的数量,因为单调栈是单调的,因此可以二分找到屏蔽建筑的分界线,进而得出可以看到的建筑物的数量。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
template <typename T, class F = function<T(const T&, const T&)>>
class SparseTable {
public:
int n;
vector<vector<T>> mat;
F func;
SparseTable(const vector<T>& a, const F& f) : func(f) {
n = static_cast<int>(a.size());
int max_log = 32 - __builtin_clz(n);
mat.resize(max_log);
mat[0] = a;
for (int j = 1; j < max_log; j++) {
mat[j].resize(n - (1 << j) + 1);
for (int i = 0; i <= n - (1 << j); i++) {
mat[j][i] = func(mat[j - 1][i], mat[j - 1][i + (1 << (j - 1))]);
}
}
}
T get(int from, int to) const { // [from, to]
assert(0 <= from && from <= to && to <= n - 1);
int lg = 32 - __builtin_clz(to - from + 1) - 1;
return func(mat[lg][from], mat[lg][to - (1 << lg) + 1]);
}
};
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, q;
cin >> n >> q;
vector<int> h(n);
for (auto& i : h)
cin >> i;
SparseTable<int> st(h, [&](int i, int j) { return max(i, j); });
vector<array<int, 2>> query(q);
for (auto& i : query)
cin >> i[0] >> i[1];
vector<int> id(q);
iota(id.begin(), id.end(), 0);
sort(id.begin(), id.end(),
[&](int a, int b) { return query[a][1] > query[b][1]; });
vector<int> ans(q);
vector<int> stack;
int cur = n - 1;
for (auto i : id) {
auto [l, r] = query[i];
--l, --r;
while (cur > r) {
int hei = h[cur];
while (!stack.empty() && hei >= stack.back()) {
stack.pop_back();
}
stack.push_back(hei);
--cur;
}
int hei = st.get(l + 1, r);
auto pos =
upper_bound(stack.begin(), stack.end(), hei, greater<int>()) -
stack.begin();
ans[i] = pos;
}
for (auto i : ans)
cout << i << '\n';
return 0;
}


G - Count Grid 3-coloring (abc379 G)

题目大意

给定h×w的格子,每个格子写着 ?123中的一种。

现将所有的?替换成123中的一个。

问有多少替换方法,使得相邻格子的数字不相同。

解题思路

从上到下,从左到右依次考虑每行每列的格子(x,y),看其?能否替换成123,取决于上一行(x1,y)的数字和上一列(x,y1)的数字是否与该格子相同。

因此我们的状态得保留这两处格子的信息,但是仅仅保留这两处的话,当前状态变成下一列时,无法继承之前的状态。

怎样才能继承之前的状态呢?那就需要保留一个轮廓的状态(即已考虑的格子的边界)。如下图蓝色格子所示。此即为轮廓线dp

example

dp[i][j][s]表示从上到下,从左到右考虑到格子 (i,j),外围一圈的数字状态为 s(这里是一个 3w的压缩状态)。

转移即考虑当前格子的数字是什么,是否合法即可。由于 hw200,因此 min(h,w)14,保持 hw,则时间复杂度为 O(hw3w),有 1e9

但考虑到s的有效 状态数没有3w(相邻相同的数字是非法状态),而是 O(2w)(因为不能和前一列相同,因此该列的数字只有两种,然后还有一些组合数的系数),因此仅从有效状态转移,时间复杂度为 O(hw2w)

神奇的代码
#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 h, w;
cin >> h >> w;
vector<string> s(h);
for (auto& i : s)
cin >> i;
if (h < w) {
vector<string> t(w, string(h, ' '));
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
t[j][i] = s[i][j];
}
}
swap(h, w);
swap(s, t);
}
vector<int> base(w, 1);
for (int i = 1; i < w; ++i) {
base[i] = base[i - 1] * 3;
}
auto get_bit = [&](int x, int y) { return x / base[y] % 3; };
auto tr = [&](int cur, int pos, int v) {
int ret = cur;
ret -= get_bit(cur, pos) * base[pos];
ret += v * base[pos];
return ret;
};
auto ok = [&](int x, int y, int cur, int v) {
if (y != 0 && get_bit(cur, y - 1) == v)
return false;
if (x != 0 && get_bit(cur, y) == v)
return false;
return true;
};
int up = 1;
for (int i = 0; i < w; i++) {
up *= 3;
}
map<int, int> dp;
dp[0] = 1;
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
map<int, int> ndp;
for (auto& [k, v] : dp) {
if (s[i][j] != '?') {
if (ok(i, j, k, s[i][j] - '1')) {
auto nxt = tr(k, j, s[i][j] - '1');
ndp[nxt] += v;
if (ndp[nxt] >= mo)
ndp[nxt] -= mo;
}
} else {
for (int l = 0; l <= 2; ++l) {
if (ok(i, j, k, l)) {
auto nxt = tr(k, j, l);
ndp[nxt] += v;
if (ndp[nxt] >= mo)
ndp[nxt] -= mo;
}
}
}
}
dp.swap(ndp);
}
}
int ans = 0;
for (auto& [k, v] : dp) {
ans += v;
if (ans >= mo)
ans -= mo;
}
cout << ans << '\n';
return 0;
}


本文作者:~Lanly~

本文链接:https://www.cnblogs.com/Lanly/p/18548361

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   ~Lanly~  阅读(149)  评论(1编辑  收藏  举报
历史上的今天:
2022-11-15 Codeforces Round #833 (Div. 2)
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.