收录范围#
ABC 340 FABC 341 DABC 346 DABC 346 EABC 346 FABC 356 DABC 356 EABC 356 F
ABC 340 F - S = 1#
给你整数 和 ( 不同时为 )。
请找出一对满足以下所有条件的整数 。不存在则输出 -1。
- 顶点位于 平面上点 的三角形的面积为 .
Solution#
易得: 构成的直线方程:
则到 的距离为:
则三角形的面积:
设 。(注意: 定义为 和 的最大公约数。)
扩展欧几里得算法,给定一个整数对 作为输入,找到一个整数对 ,使得 ,时间复杂度为 。(这里,整数对 保证是满足 的整数。)
通过将 作为扩展欧几里得算法的输入,可以获得一对 ,找到 的一组可行整数解
将 (保证 是整数)
pair<ll, ll> extgcd(ll a, ll b) {
if (!b) return {1, 0};
ll x, y;
tie(y, x) = extgcd(b, a % b);
y -= a / b * x;
return {x, y};
}
void solve() {
ll x, y;cin >> x >> y;
if (abs(__gcd(x, y)) > 2) {
cout << "-1\n";return;
}
auto [c, d] = extgcd(y, -x);
c *= 2 / abs(__gcd(x, y)), d *= 2 / abs(__gcd(x, y));
cout << c << " " << d << '\n';
}cppABC 341 D - Only one of two#
给出 ,数组 由 的倍数和 的倍数且不包含 的倍数组成的序列求
#define int long long
void solve() {
int N, M, K;
cin >> N >> M >> K;
int low = 1, high = 1e18, ans = -1;
while (low <= high) {
int mid = (high + low) / 2;
//lcm(a,b)=(a / __gcd(a, b)) * b
int count = mid / N + mid / M - 2 * (mid / ((N / __gcd(N, M)) * M));
if (count >= K) {
ans = mid;
high = mid - 1;
} else {
low = mid + 1;
}
}
cout << ans << '\n';
}cppSolution#
二分能被 和 中的一者整除的数有 个。
官方题解:
将 , 的最小公倍数记为 。
则在正整数 以下中,能被 和 同时整除的数有 个。因此,在 到 之间,能被 和 中的一者整除的数有 个。此外,由于这个数目是关于 单调递增的,因此“答案要求在 以下”和“在 到 之间,能被 和 中的一者整除的数至少有 个”即 是等价的。
因此,可以利用二分搜索来解决这个问题。在这个问题的限制下,答案一定不会超过 ,因此可以将搜索范围设为 0 到 。
答案小于等于 的证明如下:不失一般性,设 。设 , 的最大公约数为 ,,(,, 是整数)。则有
\left\lfloor \frac{X}{N}\right\rfloor+\left\lfloor \frac{X}{M}\right\rfloor-2\times \left\lfloor \frac{X}{L}\right\rfloor$>$\frac{X}{N}+\frac{X}{M}-\frac{2X}{L}-2=\frac{(m+n-2)X}{gnm}-2当 时,在问题的限制下有 ,,因此有
因此,在问题的限制下,在 以下一定至少有 个符合条件的数,特别地也就至少有 个符合条件的数。实际上,对于当前限制,上限为 ,, 时,答案为 。
具体来说,可以首先设定 ,然后只要 ,就重复以下步骤:
- 设 。
- 判断 ,如果成立,则设 ,否则设 。
最终得到的 就是答案。
在固定 时,判断 的复杂度为 ,而重复的次数最多也只有 次,因此非常高效。因此,这个问题可以用二分搜索很好地解决。
ABC 346 D - Gomamayo Sequence#
给你一个长度为 的字符串 ,它由 0 和 1 组成。
长度为 的字符串 由 0 和 1 组成,当且仅当它满足以下条件时,它是一个**好的字符串:
- 恰好有一个整数 使得 与 中的 -3 和 -3 字符相同。
对于每个 ,您可以选择是否执行一次下面的操作:
- 如果 的 个字符是 “0”,则将其替换为 “1”,反之亦然。如果执行此操作,代价为 。
求使 成为一个好字符串所需的最小总成本。
Solution#
前缀后缀和
可以知道,除了 位是相同的,其他位均为 交错的字符串。分为 和 两种情况,分别处理出前缀和后缀,则这时的答案为 或 (分别代表第 位同时为 0 或 1 且其他位为 交替字符串时所需的代价),取最小值即可。
#define int long long
void solve() {
int n;cin >> n;string s;cin >> s;vector<int> a(n + 1);s = ' ' + s;
for (int i = 1;i <= n;i++)cin >> a[i];
vector<array<int, 2>> f(n + 2), g(n + 2);
for (int i = 1;i <= n;i++) {
f[i][0] = f[i - 1][1] + (s[i] == '0' ? 0 : a[i]);
f[i][1] = f[i - 1][0] + (s[i] == '1' ? 0 : a[i]);
}
int ans = 1e18;
for (int i = n;i >= 2;i--) {
g[i][0] = g[i + 1][1] + (s[i] == '0' ? 0 : a[i]);
g[i][1] = g[i + 1][0] + (s[i] == '1' ? 0 : a[i]);
ans = min(ans, f[i - 1][0] + g[i][0]);
ans = min(ans, f[i - 1][1] + g[i][1]);
}
cout << ans << '\n';
}cppABC 346 E - Paint#
有一个行数为 列数为 的网格。初始时,所有单元格都涂有颜色 。
您将按照 的顺序执行以下操作。
-
如果是 ,则使用颜色 重新绘制 /-th 行中的所有单元格。
-
如果是 ,则用颜色 重新绘制 /th 列中的所有单元格。
完成所有操作后,针对网格中存在的每种颜色 ,找出被涂上颜色 的单元格数量。
Solution#
逆向思维
从后往前遍历,就不需要讨论的非常复杂,且后面的一定是最终的,不用担心被覆盖的问题。
#define int long long
void solve() {
int n, m, q;cin >> n >> m >> q;
vector<int> t(q + 1), a(q + 1), x(q + 1), visn(n + 1), vism(m + 1);
for (int i = 1;i <= q;i++) {
cin >> t[i] >> a[i] >> x[i];
}
int n1 = n, m1 = m;
vector<int> cnt(2e5 + 1);
for (int i = q;i >= 1;i--) {
if (t[i] == 1) {
if (!visn[a[i]]) {
n1--;
visn[a[i]] = 1;
cnt[x[i]] += m1;
}
} else {
if (!vism[a[i]]) {
m1--;
vism[a[i]] = 1;
cnt[x[i]] += n1;
}
}
}
cnt[0] += n1 * m1;
vector<pair<int, int>> ans;
for (int i = 0;i <= 2e5;i++) {
if (cnt[i]) {
ans.push_back({i,cnt[i]});
}
}
cout << ans.size() << '\n';
for (auto [x, y] : ans)cout << x << " " << y << '\n';
}cppABC 346 F - SSttrriinngg in StringString#
对于长度为 的字符串 ,我们用 表示重复 次字符串 得到的字符串,用 表示重复 次 的第一个字符、第二个字符,直到第 个字符得到的字符串。
例如,如果 abc,那么 abcabc, aaabbbccc。同时,对于任意字符串 , 和 都是空字符串。
给定正整数 以及字符串 和 。找出最大的非负整数 ,满足 是 的一个(不一定连续的)子序列。
始终是 的子序列。
Solution#
二分+贪心
[!NOTE]- 官方题解 固定 k 并考虑如何确定 g (T,k)是否为 f (S,N)的子串。
让 s 为 S 的长度,t 为 T 的长度,表示字符串 X 的前 i 个字符。T 中的每个字符也包含在 S 中(否则,答案显然为0)。
让我们依次找到以下值,以 i=1,2,…,t 的顺序:
- :最小的 j,使得 是 的子串。
最终,通过比较从 N×s 得到的 ,我们得到答案。根据 找到 相当于找出 T 的第(i+1)个字符在 f (S,N)的第()个字符(包括它自己)之后的第 k 次出现的位置,因此这个问题可以归结为处理以下查询 k 次:
- :给定正整数 a 和 b 以及字符 c,在 f (S,N)的第 a 个字符(包括它自己)之后,找到字符 c 的第 b 次出现的位置。
假设 c 在 S 中出现了 次。如果 a 大于 s,则通过 将其简化为 a≤s 的情况。如果 b 大于 ,则通过 将其简化为 b≤ 的情况。
因此,只需处理满足条件 a≤s,b≤ 的查询。如果满足这些条件,则 的答案不大于 2 s。通过为每个字符预先计算其在 f (S,2)中出现的位置,可以快速处理查询。
因此,包括最初提到的对 k 的二进制搜索,问题可以在总共 或 的时间内解决(其中 为字母表的大小)。
实现时就是把每个字符在 S + S 中出现的位置预处理出来,这样一次 query(a,b,c) 就能在常数次跳转后定位到答案;二分的判定也就随之落地。
代表的是字符串取前 个字符中的各字母个数。
的原因是当刚好整除的时候,会进位一位,到时候 z 记录的就是下一个周期的相应字符下标了,但是实际上应该记录的是上一个周期该字母的最后一个位置
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int INF = 0x3f3f3f3f;
const LL mod = 1e9 + 7;
const int N = 200005;
char s[N], t[N];
int a[N][26];
vector<int> b[26];
int main() {
LL n;
scanf("%lld%s%s", &n, s + 1, t + 1);
int m = strlen(s + 1);
for (int i = 1; i <= m; i++) {
for (int j = 0; j < 26; j++) a[i][j] = a[i - 1][j];
a[i][s[i] - 'a']++;
b[s[i] - 'a'].push_back(i);
}
LL l = 1, r = 1e17 + 500;
while (l < r) {
LL mid = l + r >> 1;
LL k = 0, z = 0;
int ok = 0;
for (int i = 1; t[i] && k < n; i++) {
int c = a[m][t[i] - 'a'];//在s字符串中这个字母的个数<->int c = b[t[i] - 'a'].size();
if (c == 0) {//若没有这个字符,则直接结束(这个mid作为答案不符合要求,答案应该更小)
k = n;break;
}
LL x = a[z][t[i] - 'a'] + mid;
k += (x - 1) / c;
x = (x - 1) % c + 1;
z = b[t[i] - 'a'][x - 1];//记录在满足条件的最后一个周期中的下标
}
if (k >= n)
//若已经经过超过了n轮,还没能将t字符串走完,则证明无法满足要求,这种二分的目标是找出第一个满足k>=n的下标
r = mid;
else
l = mid + 1;
}
printf("%lld\n", l - 1);
return 0;
}cppABC 356 D - Masked Popcount#
给定整数 和 ,计算和 . modulo 。
位运算: 根据 的性质,只有两个都为 1 时 才会增加,所以只需要枚举 为 1 的位
区间 之间若在第 位,发现是 个 0 和 个 1 交替的(即周期位 ),可以先算出 覆盖了多少个周期,在最后一个周期内又覆盖了多少个 1
很容易计算:对于第 位:
是因为这里算的个数是将 0 算入了,则后面少算了一位需要补上。
#define int long long
constexpr int mod = 998244353;
void solve() {
int n, m;cin >> n >> m;
int sum = 0;
n++;
for (int i = 0;i <= __lg(m);i++) {
if ((m >> i) & 1) {
int x = n / (1ll << (i + 1));
int y = n % (1ll << (i + 1));
sum = (sum + x * (1ll << i) + max(y - (1ll << i), 0ll)) % mod;
}
}
cout << sum << '\n';
}cppABC 356 E - Max/Min#
给定 数组,计算:
当某个数字相同的个数为 时,答案就是
当两个数不同时,对于 ,当 答案就是 。
先用一个数组 表示 在 数组中有多少个,用 表示 前缀和 表示在 中的数字个数
对于每个 ,枚举 的倍数,看区间 ,即 ,对于这种情况:
jiangly:
void solve() {
int n;cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
const int M = *max_element(a.begin(), a.end());
vector<int> c(M + 1);
for (auto x : a) {
c[x]++;
}
vector<int> s(M + 1);
for (int i = 1; i <= M; i++) {
s[i] = s[i - 1] + c[i];
}
int ans = 0;
for (int x = 1; x <= M; x++) {
ans += c[x] * (c[x] - 1) / 2;
for (int y = x; y <= M; y += x) {
int v = s[min(y + x - 1, M)] - s[max(x, y - 1)];
ans += c[x] * v * (y / x);
}
}
cout << ans << "\n";
}cppABC 356 F - Distance Component Size Query#
给你一个整数 。对于初始为空的集合 ,请依次处理以下两种类型的 查询:
1 x: 给出一个整数 。如果 在 中,则从 中删除 。否则,在 中加入 。2 x: 给出一个在 中的整数 。考虑一个图,图中的顶点是 中的数,且当且仅当两个数之间的绝对差最多为 时,这两个数之间有一条边。请打印包含 的连通部分中的顶点数。
_pbds 中的平衡树
主要思路就是建立两个集合,一个记录现在的集合,一个记录缩点后的集合。
缩点就是将每个联通块且将最大的数字缩成一个点。
这样之后若要查询 x 连通块的大小,则就是这个连通块在第一个集合中的位置减去上一个连通块在第一个集合中的位置。
ordered_set<int> s1;set<int> s2; // s1是所有点的点集 s2会把一个连通块的点缩成最大那个点
// (s1要求rank,所以必须用PBDS,s2普通set也可以)
void solve() {
ll q, k;cin >> q >> k;
// 这里插几个哨兵是为了方便出题,边界上的prev next 就能统一了
s1.insert(-4e18), s1.insert(4e18);
s2.insert(-4e18), s2.insert(4e18);
while (q--) {
ll op, x;cin >> op >> x;
if (op == 1) {
if (s1.find(x) == s1.end()) {
auto it = s1.insert(x).first;
ll w1 = *prev(it), w2 = *next(it); // x左右两个点编号
if (x - w1 <= k) s2.erase(w1);
if (w2 - x <= k)
; // x就不用插入s2了
else
s2.insert(x);
} else {
auto it = s1.find(x);
ll w1 = *prev(it), w2 = *next(it); // x左右两个点编号
s1.erase(x), s2.erase(x); // x在s2里不存在也没影响
if (w2 - w1 > k) s2.insert(w1); // 让w1复活 (原来是活的也不影响)
}
} else {
auto it = s2.lower_bound(x); // 找到x所在连通块最大的点
cout << s1.order_of_key(*it) - s1.order_of_key(*prev(it)) << '\n';
}
}
}cpp