Codeforces Round 905(div3)解题报告
A. Morning
打的时候没看懂题面 跳了
题目大意
你有一个光标 初始值为1 对于1234567890 你每次可以花费1时间将光标上的数切换成相邻的数
也可以花费1时间将当前光标上的数推入一个队列里
T次询问 每次求构造出一个长度为4的指定队列的最小花费
Solution
首先4次显示一定要花费4的时间
如果把所有0都替换为10 那么切换到第一个数的花费就是 \(\left|a_1 - 1\right|\)
否则 切换到该数的花费就是 \(\left|a_i - a_{i-1}\right|\)
时间复杂度 \(\text{O}(4T)\)
点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
int xr = 0, F = 1;
char cr;
while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
while (cr >= '0' && cr <= '9')
xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
return xr * F;
}
inline void write(ll x) {
int wtop = 0;
char stk[51];
if (x < 0) putchar('-'), x = -x;
do {
stk[++wtop] = x % 10 + '0';
x /= 10;
} while (x);
for (int i = wtop; i; --i) putchar(stk[i]);
}
namespace steven24 {
const int N = 5;
int T;
int a[N];
char s[N];
void main() {
T = read();
while (T--) {
scanf("%s", s);
for (int i = 1; i <= 4; ++i) a[i] = s[i - 1] - '0';
for (int i = 1; i <= 4; ++i) if (!a[i]) a[i] = 10;
int ans = 0;
ans += abs(a[1] - 1);
for (int i = 2; i <= 4; ++i) ans += abs(a[i] - a[i - 1]);
write(ans + 4), putchar('\n');
}
}
}
int main() {
steven24::main();
return 0;
}
B.Chemistry
题目大意
给定一个长度为 \(n (1 \le n \le 10^5)\) 的字符串 给定一个 \(k (0 \le k \le n)\)
你需要在这个字符串里删除恰好 \(k\) 个字符 并且把剩余字符重新排列成一个回文串
问是否可以做到
Solution
因为字符串可以重新排列 所以我们关心的只是剩余每个字符的出现次数
首先 \(n - k\) 可以求得剩余字符串的长度
如果剩余长度为奇数 那么每个字符出现的数量一定是一个字符为奇数 剩余的都为偶数
否则 所有字符的出现次数都应该为偶数
所以我们直接关心原串中出现次数为奇数的字符有多少个 并且能否用 \(k\) 次删除操作把它们变成目标状态
具体的 假设我们要求删完后有 \(s\) 个字符出现次数为奇数 原串中有 \(t\) 个字符出现次数为奇数
那么我们比较 \(t - s\) 与 \(k\) 的大小关系即可 至于多出来的一部分 直接两个两个删除消耗掉即可
时间复杂度 \(\text{O}(\sum n)\)
点击查看代码
#include <bits/stdc++.h>
using namespace std;
inline int read() {
int xr = 0, F = 1;
char cr;
while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
while (cr >= '0' && cr <= '9')
xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
return xr * F;
}
namespace steven24 {
int c[31];
int T, n, k;
void main() {
T = read();
while (T--) {
memset(c, 0, sizeof c);
n = read(), k = read();
for (int i = 1; i <= n; ++i) {
char cc;
scanf(" %c", &cc);
++c[cc - 'a'];
}
int cnt = 0;
for (int i = 0; i <= 25; ++i) if (c[i] & 1) ++cnt;
if ((n - k) & 1) {
if (cnt - 1 <= k) puts("YES\n");
else puts("NO\n");
} else {
if (cnt <= k) puts("YES\n");
else puts("NO\n");
}
}
}
}
int main() {
steven24::main();
return 0;
}
C.Raspberries
product 竟然还有乘积的意思
题目大意
给定一个长度为 \(n(2 \le n \le 10^5)\) 的序列
再给定一个 \(k(2 \le k \le 5)\)
每次操作可以选择序列中的一个数并把它+1
问使这个序列中所有数的乘积为 \(k\) 的倍数 最少需要操作多少次
Solution
考虑构造 显然要让这个序列中的每个数都拿出来一个因数 然后乘起来恰好为 \(k\)
发现 \(k\) 只有2,3,4,5四种取值 其中2,3,5均为质数
这也就意味着 在这种情况下 如果序列中存在一个数为 \(k\) 的倍数 那么答案就为0
否则就找一个能达到 \(k\) 的倍数所需要操作的最少的一个数即可 操作步数为 \(k - (x \mod k)\)
我们单独考虑 \(k = 4\) 的情况
- 如果存在一个为4的倍数 答案就为0
- 否则 如果存在两个为2的倍数 答案也为0
- 否则 如果存在一个为2的倍数 答案就为达到2的倍数需要操作最少的数
- 否则 如果一个为2的倍数的数都没有 答案就为使两个数变为2的倍数/使一个数变为4的倍数取 \(\min\)
时间复杂度 \(\text{O}(\sum n)\)
点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
int xr = 0, F = 1;
char cr;
while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
while (cr >= '0' && cr <= '9')
xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
return xr * F;
}
void write(ll x) {
char ws[51];
int wt = 0;
if (x < 0) putchar('-'), x = -x;
do {
ws[++wt] = x % 10 + '0';
x /= 10;
} while (x);
for (int i = wt; i; --i) putchar(ws[i]);
}
namespace steven24 {
const int N = 2e5 + 0721;
int step[N], a[N];
int T, n, k;
void main() {
T = read();
while (T--) {
n = read(), k = read();
if (k != 4) {
for (int i = 1; i <= n; ++i) {
int x = read();
step[i] = (k - x % k) % k;
}
int ans = *min_element(step + 1, step + 1 + n);
write(ans), putchar('\n');
} else {
int cnt = 0;
for (int i = 1; i <= n; ++i) {
a[i] = read();
while (!(a[i] & 1)) ++cnt, a[i] >>= 1;
}
if (cnt >= 2) puts("0\n");
else if (cnt == 1) puts("1\n");
else {
bool flag = 1;
for (int i = 1; i <= n; ++i) {
if ((a[i] + 1) % 4 == 0) {
puts("1\n");
flag = 0;
break;
}
}
if (flag) puts("2\n");
}
}
}
}
}
int main() {
steven24::main();
return 0;
}
D.In Love
做的比B和C快多了
题目大意
维护一个可重集合 有 \(q\) 次操作 每次操作为以下两种之一
- 加入一个区间 \(\left[l, r\right]\)
- 删除一个区间 \(\left[l, r\right]\) 保证至少存在一个该区间
每次操作后 你需要回答是否存在两个区间 使它们不相交
\(1 \le q \le 10^5, 1 \le l \le r \le 10^9\)
Solution
考虑维护一棵线段树
对于每次加入操作 执行区间+1
对于每次删除操作 执行区间-1
再记录一下当前一共有多少个区间
查询全局最大值是否为区间总数即可
记得先全存下来然后离散化
时间复杂度 \(\text{O}(2q \log V + q \log 2q)\)
点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
int xr = 0, F = 1;
char cr;
while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
while (cr >= '0' && cr <= '9')
xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
return xr * F;
}
void write(ll x) {
char ws[51];
int wt = 0;
if (x < 0) putchar('-'), x = -x;
do {
ws[++wt] = x % 10 + '0';
x /= 10;
} while (x);
for (int i = wt; i; --i) putchar(ws[i]);
}
namespace steven24 {
const int N = 2e5 + 0721;
int b[N];
int q, n, sum;
struct node {
int l, r;
char opt;
} a[N];
void discretization() {
int cnt = 0;
for (int i = 1; i <= q; ++i) {
b[++cnt] = a[i].l;
b[++cnt] = a[i].r;
}
sort(b + 1, b + 1 + cnt);
n = unique(b + 1, b + 1 + cnt) - b - 1;
for (int i = 1; i <= q; ++i) {
a[i].l = lower_bound(b + 1, b + 1 + n, a[i].l) - b;
a[i].r = lower_bound(b + 1, b + 1 + n, a[i].r) - b;
}
}
struct segment_tree {
#define ls (k << 1)
#define rs (k << 1 | 1)
#define mid ((l + r) >> 1)
int tr[N << 2], lazy[N << 2];
inline void add(int k, int val) {
tr[k] += val;
lazy[k] += val;
}
void pushdown(int k) {
add(ls, lazy[k]);
add(rs, lazy[k]);
lazy[k] = 0;
}
inline void pushup(int k) {
tr[k] = max(tr[ls], tr[rs]);
}
void modify(int k, int l, int r, int u, int v, int val) {
if (u <= l && v >= r) {
add(k, val);
return;
}
if (lazy[k]) pushdown(k);
if (u <= mid) modify(ls, l, mid, u, v, val);
if (v > mid) modify(rs, mid + 1, r, u, v, val);
pushup(k);
}
} seg;
void main() {
q = read();
for (int i = 1; i <= q; ++i) scanf(" %c", &a[i].opt), a[i].l = read(), a[i].r = read();
discretization();
for (int i = 1; i <= q; ++i) {
if (a[i].opt == '+') seg.modify(1, 1, n, a[i].l, a[i].r, 1), ++sum;
else seg.modify(1, 1, n, a[i].l, a[i].r, -1), --sum;
if (seg.tr[1] == sum) puts("NO\n");
else puts("YES\n");
}
}
}
int main() {
steven24::main();
return 0;
}
E.Look Back
怎么赛时会被这题卡
题目大意
给定一个长度为 \(n(1 \le n \le 10^5)\) 每次操作可以选择一个数并把它 \(\times 2\)
问将原序列变为一个单调不降序列的最小操作次数
\(\sum n \le 2 \times 10^5, 1 \le a_i \le 10^9\)
Solution
首先有一个比较显然的贪心:从第2个数开始从前往后考虑每一位 每次把一个数变为 \(\times 2\) 以后刚刚好不小于前一个数的样子
证明:
如果此时再将它扩大一倍 那么后一个数可能需要额外扩大一倍 可能不需要 但这样一定不优
这样看起来复杂度是 \(\text{O}(n \log V)\) 的 但是真正模拟起来会因为每次扩大而导致的值域扩大二倍让这个复杂度假掉 甚至会直接炸 long long
我们考虑优化这个过程 仅记录上一位扩大了多少次 那么对于当前位我们只需要计算偏移值即可
具体地 设上一位操作的次数为 \(g_{i - 1}\)
如果 \(a_i < a_{i - 1}\) 算出将 \(a_i\) 扩大至不小于 \(a_{i - 1}\) 需要 \(x\) 次操作 则 \(g_{i} = g_{i - 1} + x\)
如果 \(a_i > a_{i - 1}\) 那么我们或许有可能能少扩大几次 所以此事算出将 \(a_{i - 1}\) 扩大至刚好不大于 \(a_{i}\) 需要 \(x\) 次操作 则 \(g_{i} = g_{i - 1} - x\)
注意至少要扩大0次 否则会干扰后续计算 所以算完 \(g_i\) 还要和0取个 \(\max\)
答案即为 \(\sum\limits_{i = 1}^n g_i\)
时间复杂度就是一个真正的 \(\text{O}(n \log V)\)
点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read() {
ll xr = 0, F = 1;
char cr;
while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
while (cr >= '0' && cr <= '9')
xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
return xr * F;
}
void write(ll x) {
char ws[51];
int wt = 0;
if (x < 0) putchar('-'), x = -x;
do {
ws[++wt] = x % 10 + '0';
x /= 10;
} while (x);
for (int i = wt; i; --i) putchar(ws[i]);
}
namespace steven24 {
const int N = 1e5 + 0721;
ll a[N], g[N];
int n, T;
ll ans;
void main() {
T = read();
while (T--) {
n = read();
for (int i = 2; i <= n; ++i) g[i] = 0;
for (int i = 1; i <= n; ++i) a[i] = read();
ans = 0;
for (int i = 2; i <= n; ++i) {
ll tmp = a[i];
while (tmp < a[i - 1]) {
tmp <<= 1;
++g[i];
// cout << i << " " << tmp << " " << g[i] <<"\n";
}
tmp = a[i - 1];
while ((tmp << 1) <= a[i]) {
tmp <<= 1;
--g[i];
}
g[i] += g[i - 1];
g[i] = max(0ll, g[i]);
ans += g[i];
// cout << i << " " << g[i] << "\n";
}
write(ans), putchar('\n');
}
}
}
int main() {
steven24::main();
return 0;
}
F.You are so beautiful
卡div2 C.jpg
题目大意
给定一个长度为 \(n(1 \le n \le 10^5)\) 的序列
询问有多少子区间 \(b = \left[a_l, a_{l + 1}, ..., a_{r}\right]\) 满足不存在任何一个其他的 \(a\) 的子序列 \(c\) 与 \(b\) 相同
\(\sum n \le 2 \times 10^5, 1 \le a_i \le 10^9\)
Solution
注意到每个对于每个合法的 \(b\) 而言 \(l\) 一定是 \(a_l\) 出现的最左的位置 而 \(r\) 一定是 \(a_r\) 出现的最右的位置 这样就不会再存在在 \(\left[l, r\right]\) 以外的 \((i, j)\) 使得 \(a_i = a_l, a_j = a_r\) 进而使得 \(c = \left[a_i, a_{l + 1}, ..., a_j\right] = b\)
所以我们直接开一个数组 记录这个位置是否为该值出现的最左的位置
然后我们枚举右端点 对于每个合法的右端点 合法的左端点数量就是一段前缀和
该过程可以用 set 配合 BIT 来实现
时间复杂度 \(\text{O}(n \log n)\)
点击查看代码
#include <bits/stdc++.h>
#pragma GCC optimize(2)
#define ll long long
using namespace std;
inline ll read() {
ll xr = 0, F = 1;
char cr;
while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
while (cr >= '0' && cr <= '9')
xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
return xr * F;
}
void write(ll x) {
char ws[51];
int wt = 0;
if (x < 0) putchar('-'), x = -x;
do {
ws[++wt] = x % 10 + '0';
x /= 10;
} while (x);
for (int i = wt; i; --i) putchar(ws[i]);
}
namespace steven24 {
const int N = 1e5 + 0721;
set<int> s1, s2;
int a[N];
int T, n;
ll ans;
struct BIT {
int tr[N];
inline int lowbit(int x) {
return x & (-x);
}
void update(int x, int val) {
while (x <= n) {
tr[x] += val;
x += lowbit(x);
}
}
int query(int x) {
int ret = 0;
while (x) {
ret += tr[x];
x -= lowbit(x);
}
return ret;
}
} bit;
void init() {
for (int i = 1; i <= n; ++i) bit.tr[i] = 0;
s1.clear();
s2.clear();
ans = 0;
}
void main() {
T = read();
while (T--) {
n = read();
init();
for (int i = 1; i <= n; ++i) a[i] = read();
for (int i = 1; i <= n; ++i) {
if (s1.find(a[i]) == s1.end()) {
s1.insert(a[i]);
bit.update(i, 1);
}
}
for (int i = n; i >= 1; --i) {
if (s2.find(a[i]) == s2.end()) {
s2.insert(a[i]);
ans += bit.query(i);
}
}
write(ans), putchar('\n');
}
}
}
int main() {
steven24::main();
return 0;
}

浙公网安备 33010602011771号