Codeforces Round #797 (Div. 3)
打工人下班晚了,到家只剩一个小时了,做完最后两题夜宵外卖到了,前面的题先咕咕了。
F. Shifting String
题意
给定一个长度为\(n\)的字符串\(s\)和一个长度为\(n\)的排列\(p\)。
定义乘法:字符串\(s_1s_2\dots s_n\)乘上排列\(p\)等于\(s_{p_1}s_{p_2}\dots s_{p_n}\)。
问经过多少次乘法之后\(s\)首次变回原串?
其中\(1 \le n \le 200\)。
思路
排列可以看成置换,然后就想到拆成多个子群,子群答案的LCM就是答案。
长度为\(l\)的子群最多\(l\)次操作就变回原样了。
然后就是模拟了,复杂度\(O(n^3)\)。
AC代码
// Problem: F. Shifting String
// Contest: Codeforces - Codeforces Round #797 (Div. 3)
// URL: https://codeforces.com/contest/1690/problem/F
// Memory Limit: 256 MB
// Time Limit: 3000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#define CPPIO std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
#define freep(p) p ? delete p, p = nullptr, void(1) : void(0)
#ifdef BACKLIGHT
#include "debug.h"
#else
#define logd(...) ;
#endif
using i64 = int64_t;
using u64 = uint64_t;
void solve_case(int Case);
int main(int argc, char* argv[]) {
CPPIO;
int T = 1;
std::cin >> T;
for (int t = 1; t <= T; ++t) {
solve_case(t);
}
return 0;
}
void solve_case(int Case) {
logd(Case);
int n;
std::cin >> n;
std::string s;
std::cin >> s;
std::vector<int> p(n);
for (int i = 0; i < n; ++i) {
std::cin >> p[i];
--p[i];
}
int64_t ans = 1;
std::vector<int> v(n, 0);
for (int i = 0; i < n; ++i) {
if (v[i] == 1)
continue;
std::vector<int> a;
int j = i;
while (!v[j]) {
a.push_back(j);
v[j] = 1;
j = p[j];
}
logd(a);
int64_t c = 0;
std::string t = s;
do {
++c;
std::string temp = t;
for (int j : a) {
t[j] = temp[p[j]];
}
} while (t != s);
ans = std::lcm(ans, c);
}
std::cout << ans << "\n";
}
G. Count the Trains
题意
给定一个长度为\(n\)的数组\(a\),还有\(m\)次操作\((x_i, d_i)\),每次令\(a_{x_i} = a_{x_i} - d_i\)。
要求每次操作过后输出\(a\)的前缀最小值数组中有多少个不同的值。
其中\(1 \le n, m \le {10}^5, 0 \le a_i \le {10}^9\)。
思路
记\(a\)的前缀最小值数组为\(s\),易得\(s\)单调非增。
维护一个数组\(w\),如果\(s_x\)是连续相同元素中的第一个,则\(w_x = 1\),否则\(w_x = 0\)。易得\(w\)的元素和即为答案。
\(a_x\)修改之后,如果\(a_x < s_x\),且\(r\)等于\(s\)中满足\(a_i \ge a_x\)的最大的\(i\),那么\(s_x \dots s_r\)都需要更新成\(a_x\),\(w_x\)需要更新成\(1\),\(w_{x+1} \dots w_r\)需要更新成\(0\)。
涉及的操作如区间置数等都是线段树基本操作。\(r\)的话稍微复杂一点点,线段树结合二分或者直接线段树上二分就能求。
然后就是\(s\)和\(w\)分别用线段树维护,然后模拟一下就可以了。
AC代码
// Problem: G. Count the Trains
// Contest: Codeforces - Codeforces Round #797 (Div. 3)
// URL: https://codeforces.com/contest/1690/problem/G
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#define CPPIO std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
#define freep(p) p ? delete p, p = nullptr, void(1) : void(0)
#ifdef BACKLIGHT
#include "debug.h"
#else
#define logd(...) ;
#endif
using i64 = int64_t;
using u64 = uint64_t;
void solve_case(int Case);
int main(int argc, char* argv[]) {
CPPIO;
int T = 1;
std::cin >> T;
for (int t = 1; t <= T; ++t) {
solve_case(t);
}
return 0;
}
const int N = 1e5 + 5;
int n, m, a[N], s[N];
namespace S {
int mi[N << 2], tag[N << 2];
void push_up(int x) {
mi[x] = std::min(mi[x << 1], mi[x << 1 | 1]);
}
void change(int x, int v) {
mi[x] = tag[x] = v;
}
void push_down(int x) {
if (tag[x] != INT_MAX) {
change(x << 1, tag[x]);
change(x << 1 | 1, tag[x]);
tag[x] = INT_MAX;
}
}
void build(int x, int l, int r) {
tag[x] = INT_MAX;
if (l == r) {
mi[x] = s[l];
return;
}
int mid = (l + r) >> 1;
build(x << 1, l, mid);
build(x << 1 | 1, mid + 1, r);
push_up(x);
}
void set(int x, int l, int r, int L, int R, int v) {
if (l == L && r == R) {
change(x, v);
return;
}
push_down(x);
int mid = (l + r) >> 1;
if (R <= mid)
set(x << 1, l, mid, L, R, v);
else if (L > mid)
set(x << 1 | 1, mid + 1, r, L, R, v);
else {
set(x << 1, l, mid, L, mid, v);
set(x << 1 | 1, mid + 1, r, mid + 1, R, v);
}
push_up(x);
}
int query(int x, int l, int r, int L, int R) {
if (l == L && r == R) {
return mi[x];
}
push_down(x);
int mid = (l + r) >> 1;
if (R <= mid)
return query(x << 1, l, mid, L, R);
else if (L > mid)
return query(x << 1 | 1, mid + 1, r, L, R);
else
return std::min(query(x << 1, l, mid, L, mid), query(x << 1 | 1, mid + 1, r, mid + 1, R));
}
int getLast(int v) {
int l = 1, r = n, mid, k = n;
while (l <= r) {
mid = (l + r) >> 1;
if (query(1, 1, n, mid, mid) >= v)
l = mid + 1, k = mid;
else
r = mid - 1;
}
return k;
}
} // namespace S
namespace W {
int sum[N << 2], tag[N << 2];
void push_up(int x) {
sum[x] = sum[x << 1] + sum[x << 1 | 1];
}
void change(int x, int l, int r, int v) {
sum[x] = (r - l + 1) * v;
tag[x] = v;
}
void push_down(int x, int l, int r) {
if (tag[x] != INT_MAX) {
int mid = (l + r) >> 1;
change(x << 1, l, mid, tag[x]);
change(x << 1 | 1, mid + 1, r, tag[x]);
tag[x] = INT_MAX;
}
}
void build(int x, int l, int r) {
tag[x] = INT_MAX;
if (l == r) {
sum[x] = 0;
return;
}
int mid = (l + r) >> 1;
build(x << 1, l, mid);
build(x << 1 | 1, mid + 1, r);
push_up(x);
}
void set(int x, int l, int r, int L, int R, int v) {
if (l == L && r == R) {
change(x, l, r, v);
return;
}
push_down(x, l, r);
int mid = (l + r) >> 1;
if (R <= mid)
set(x << 1, l, mid, L, R, v);
else if (L > mid)
set(x << 1 | 1, mid + 1, r, L, R, v);
else {
set(x << 1, l, mid, L, mid, v);
set(x << 1 | 1, mid + 1, r, mid + 1, R, v);
}
push_up(x);
}
} // namespace W
void solve_case(int Case) {
std::cin >> n >> m;
for (int i = 1; i <= n; ++i)
std::cin >> a[i];
s[1] = a[1];
for (int i = 2; i <= n; ++i) {
if (a[i] < s[i - 1]) {
s[i] = a[i];
} else {
s[i] = s[i - 1];
}
}
S::build(1, 1, n);
W::build(1, 1, n);
W::set(1, 1, n, 1, 1, 1);
for (int i = 2; i <= n; ++i) {
if (s[i] != s[i - 1]) {
W::set(1, 1, n, i, i, 1);
}
}
for (int _ = 1; _ <= m; ++_) {
int x, y;
std::cin >> x >> y;
a[x] -= y;
logd(x, y);
int z = S::query(1, 1, n, x, x);
if (a[x] < z) {
int l = x, r = S::getLast(a[x]);
S::set(1, 1, n, l, r, a[x]);
W::set(1, 1, n, l, l, 1);
if (l + 1 <= r)
W::set(1, 1, n, l + 1, r, 0);
}
std::cout << W::sum[1] << " ";
}
std::cout << "\n";
}