2024-03-23 23:35阅读: 722评论: 0推荐: 2

AtCoder Beginner Contest 346

A - Adjacent Product (abc346 A)

题目大意

给定n个数,依次输出相邻俩数的乘积。

解题思路

按照题意模拟即可。

神奇的代码
#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 la = 0;
cin >> la;
for (int i = 1; i < n; i++) {
int a;
cin >> a;
cout << la * a << '\n';
la = a;
}
return 0;
}


B - Piano (abc346 B)

题目大意

给定一个由wbwbwwbwbwbw无限拼接的字符串。

给定w,b,问是否由一个子串,其有 wwbb

解题思路

考虑枚举子串的起点,其实只有len(wbwbwwbwbwbw)=12种情况。

枚举起点后,统计其后的w+b个字符,看看是否满足上述需求即可。

时间复杂度是 O(12(w+b))

神奇的代码
#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 = "wbwbwwbwbwbw";
int w, b;
cin >> w >> b;
int len = w + b;
bool ok = false;
for (int i = 0; i < s.size(); ++i) {
map<char, int> cc;
for (int j = i, cnt = 0; cnt < len; j = (j + 1) % s.size(), ++cnt) {
cc[s[j]]++;
}
if (cc['w'] == w && cc['b'] == b) {
ok = true;
break;
}
}
if (ok)
cout << "Yes" << endl;
else
cout << "No" << endl;
return 0;
}


C - Σ (abc346 C)

题目大意

给定n个数 ai

1k中,不是 ai的数的和。

解题思路

先计算\suni=1ki=k(k+1)2,然后减去 ai中出现过的数即可。注意要对 ai去重,可以先 sortunique,或者直接丢到 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, k;
cin >> n >> k;
vector<int> a(n);
for (auto& i : a)
cin >> i;
LL ans = 1ll * k * (k + 1) / 2;
for (auto& i : set<int>(a.begin(), a.end())) {
if (i <= k) {
ans -= i;
}
}
cout << ans << '\n';
return 0;
}


D - Gomamayo Sequence (abc346 D)

题目大意

给定一个01字符串s,对第 i位翻转需要 ci的代价。

定义一个好的字符串,当且仅当只有一个相邻位置上的数字是相同的。

问将字符串变成好的字符串的最小代价。

解题思路

注意到好的字符串的情况数只有O(n)个,其中 n是串 s的长度。因此我们可以枚举所有情况。

枚举相邻数字相同的位置,然后计算变成 010101.....101010...这两种情况的代价,所有位置情况代价取最小值即为答案。

如何快速计算代价?考虑到由相邻数字相同的位置左右分割开来的都是固定的010101101010,因此事先预处理所有前缀和后缀变换成 010101101010的代价后,通过前缀代价+后缀代价,就可以O(1)得到当前枚举的情况的代价。

神奇的代码
#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;
vector<int> c(n);
for (auto& i : c)
cin >> i;
vector<array<LL, 2>> pre(n + 1), suff(n + 1);
const string expect = "01";
for (int i = 0; i < n; ++i) {
pre[i][0] =
(s[i] == expect[i & 1] ? c[i] : 0) + (i > 0 ? pre[i - 1][0] : 0);
pre[i][1] =
(s[i] == expect[~i & 1] ? c[i] : 0) + (i > 0 ? pre[i - 1][1] : 0);
}
for (int i = n - 1; i >= 0; --i) {
suff[i][0] = (s[i] == expect[i & 1] ? c[i] : 0) +
(i < n - 1 ? suff[i + 1][0] : 0);
suff[i][1] = (s[i] == expect[~i & 1] ? c[i] : 0) +
(i < n - 1 ? suff[i + 1][1] : 0);
}
LL ans = 1e18 + 7;
;
for (int i = 0; i < n - 1; ++i) {
ans =
min({ans, pre[i][0] + suff[i + 1][1], pre[i][1] + suff[i + 1][0]});
}
cout << ans << '\n';
return 0;
}


E - Paint (abc346 E)

题目大意

h×w的平面,格子初始全为颜色 0

依次进行 m次操作,每次操作将某一行或某一列的格子涂成颜色 xi

问最后各个颜色的格子数量。

解题思路

朴素的想法,最后统计每块格子的颜色,时间复杂度避免不了为为O(hw)

考虑到每次操作都是对一行或一列涂色,其涂的格子数是已知的,所以可以直接累计结果。

但这样的问题是,后面的操作会影响到前面的结果,颜色会覆盖,导致先前涂的颜色数量可能会减少。

注意到后面的操作会覆盖前面的操作,如果我们对操作反过来考虑,先考虑最后一次操作,再考虑前一个操作,那么后考虑的操作不会影响先考虑的操作,就不会出现上面的先前涂的颜色会减少的问题。

因此我们对操作倒过来考虑,每次操作所涂的格子数。格子数的求法比较简单,比如一次操作对某一行涂色,其涂得格子数为w已经涂过的列操作数。维护一下已经涂过的列和行数量即可。当然还要维护某一列和某一行是否被涂过,后涂的操作是无效的。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int up = 2e5 + 8;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int h, w, m;
cin >> h >> w >> m;
vector<array<int, 3>> op(m);
for (auto& [t, a, x] : op) {
cin >> t >> a >> x;
--a;
}
vector<LL> cnt(up, 0);
vector<int> used_row(h, 0);
vector<int> used_col(w, 0);
int row = h, col = w;
reverse(op.begin(), op.end());
for (auto& [t, a, x] : op) {
if (t == 1) {
if (used_row[a])
continue;
used_row[a] = 1;
--row;
cnt[x] += col;
} else {
if (used_col[a])
continue;
used_col[a] = 1;
--col;
cnt[x] += row;
}
}
cnt[0] += 1ll * h * w - accumulate(cnt.begin(), cnt.end(), 0ll);
vector<pair<int, LL>> ans;
for (int i = 0; i < up; ++i) {
if (cnt[i] != 0)
ans.emplace_back(i, cnt[i]);
}
cout << ans.size() << '\n';
for (auto& [i, c] : ans) {
cout << i << " " << c << '\n';
}
return 0;
}


F - SSttrriinngg in StringString (abc346 F)

题目大意

给定两个字符串s,t,定义 f(s,n)表示将字符串 s重复拼接 n次。 g(t,k)表示将 t的每个字符重复 k次得到。

给定 n,问最大的 k,使得 g(t,k)f(s,n)的子序列。

解题思路

考虑k怎么确定,如果我枚举 k,那剩下的问题就是判断子序列,容易发现这个可以在 O(|t|)内判断。但 k最高可高达 1017,枚举不现实。

注意到k越小越好满足, k越大越难满足是子序列,容易发现k是否是子序列具有单调性,因此可以二分k

二分k后,就按照匹配子序列那样(就近匹配)的暴力匹配 t的每个字符即可。只是细节有点多。

匹配 t的每个字符时,每个字符ck个,假设 s有该字符 cntc个,那先每 cntccntc个匹配,耗掉 kcntcs,然后剩下的 k%cntcc匹配 新一份的s的一部分。此时剩下的 字符就匹配t的下一个字符。

因此二分验证时需要维护信息有:

  • 当前匹配的是 t的第cur个字符
  • 当前字符还剩下 left个要匹配,
  • 当前用了 sums
  • 当前份正匹配到第it个字符。
神奇的代码
#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);
LL n;
string s, t;
cin >> n >> s >> t;
vector<vector<int>> pos(26);
for (int i = 0; i < s.size(); ++i) {
pos[s[i] - 'a'].push_back(i);
}
LL l = 0, r = 1e18;
auto check = [&](LL x) {
if (x == 0)
return true;
LL sum = 0;
int cur = 0;
int it = 0;
LL left = x;
while (sum <= n && cur < t.size()) {
int c = t[cur] - 'a';
if (pos[c].size() == 0)
return false;
if (it == 0) {
int cnt = pos[c].size();
LL cost = left / cnt;
sum += cost;
left -= cost * cnt;
LL mod = left % cnt;
if (mod != 0) {
sum += 1;
it = (pos[c][mod - 1] + 1) % s.size();
left -= mod;
} else {
it = (pos[c].back() + 1) % s.size();
}
} else {
int st = lower_bound(pos[c].begin(), pos[c].end(), it) -
pos[c].begin();
int cnt = pos[c].size() - st;
if (left > cnt) {
left -= cnt;
it = 0;
} else {
it = (pos[c][st + left - 1] + 1) % s.size();
left = 0;
}
}
if (left == 0) {
++cur;
left = x;
}
}
return sum <= n;
};
while (l + 1 < r) {
LL mid = (l + r) >> 1;
if (check(mid))
l = mid;
else
r = mid;
}
cout << l << '\n';
return 0;
}


G - Alone (abc346 G)

题目大意

给定n个数ai。问 (l,r)的数量,满足a[l..r]中有数字仅出现一次。

解题思路

几个常规思路,比如枚举r, 看有几个符合条件的l,或者分治之类的,都不行,棘手在于有数字仅出现一次这一条件不好处理。

那就尝试枚举 仅出现一次的数字,记该数字x的左右两边最近的数字x 的位置lx,rx,那么所有满足 l[lx+1,i],r[i,rx1] 条件的区间(l,r)都是满足题意的区间,个数即为两个可行区间大小的乘积。

所有的这样的区间个数加起来,但不是答案,会有算重的。比如一个区间 (l,r)包含了两个仅出现一次的数字,那么这个区间会算成两份。关键是如何去重。

如何理解去重,设想一个二维平面,横坐标是l,纵坐标是 r,那么上述的每一个 l[lx+1,i],r[i,rx1] 就对应一个矩形,矩形之间可能有交。答案就是矩形的面积。

从这个角度理解的话,解法就呼之欲出了:扫描线扫一遍就是答案了。

代码实现里,因为扫描线是枚举一维(比如r),用线段树维护另一维(l 的可行位置个数),而这里r最大只有 n,因此可以直接枚举,算一层一层的结果,而不必像平时扫描线里的离散化之类的。枚举每个 r之后,更新 l的可行区域。

到最后其实就是枚举 r,看有几个符合条件的l这样的思路,只是中间怎么维护比较难思考,理解矩形面积+扫描线后就能更好想到做法了。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 2e5 + 8;
class segment {
#define lson (root << 1)
#define rson (root << 1 | 1)
public:
LL minn[N << 2];
LL cnt[N << 2];
LL lazy[N << 2];
void build(int root, int l, int r) {
if (l == r) {
minn[root] = 0;
lazy[root] = 0;
cnt[root] = 1;
return;
}
int mid = (l + r) >> 1;
build(lson, l, mid);
build(rson, mid + 1, r);
lazy[root] = 0;
}
void pushdown(int root) {
if (lazy[root]) {
minn[lson] += lazy[root];
minn[rson] += lazy[root];
lazy[lson] += lazy[root];
lazy[rson] += lazy[root];
lazy[root] = 0;
}
}
void update(int root, int l, int r, int L, int R, LL val) {
if (L <= l && r <= R) {
minn[root] += val;
lazy[root] += val;
return;
}
pushdown(root);
int mid = (l + r) >> 1;
if (L <= mid)
update(lson, l, mid, L, R, val);
if (R > mid)
update(rson, mid + 1, r, L, R, val);
minn[root] = min(minn[lson], minn[rson]);
cnt[root] = (minn[lson] == minn[root] ? cnt[lson] : 0) +
(minn[rson] == minn[root] ? cnt[rson] : 0);
}
pair<int, int> query(int root, int l, int r, int L, int R) {
if (L <= l && r <= R) {
return {minn[root], cnt[root]};
}
pushdown(root);
int mid = (l + r) >> 1;
pair<int, int> resl = {INT_MAX, 0}, resr = {INT_MAX, 0};
if (L <= mid)
resl = query(lson, l, mid, L, R);
if (R > mid)
resr = query(rson, mid + 1, r, L, R);
pair<int, int> res;
res.first = min(resl.first, resr.first);
res.second = (resl.first == res.first ? resl.second : 0) +
(resr.first == res.first ? resr.second : 0);
return res;
}
} sg;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
vector<int> a(n);
for (auto& x : a) {
cin >> x;
--x;
}
sg.build(1, 1, n);
vector<vector<int>> pos(n, vector<int>(1, 0));
LL ans = 0;
for (int i = 0; i < n; i++) {
auto& history = pos[a[i]];
int la = history.back();
if (history.size() > 1) {
int lla = history[history.size() - 2];
sg.update(1, 1, n, lla + 1, la, -1);
}
int cur = i + 1;
sg.update(1, 1, n, la + 1, cur, 1);
auto [minn, cnt] = sg.query(1, 1, n, 1, cur);
ans += cur - (minn == 0 ? cnt : 0);
history.push_back(cur);
}
cout << ans << '\n';
return 0;
}


本文作者:~Lanly~

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

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

posted @   ~Lanly~  阅读(722)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.