C.Mark and His Unfinished Essay
题目大意:给定一个长度为 n n n的字符串, 1 ≤ n ≤ 2 × 1 0 5 1\le n\le2\times10^5 1≤n≤2×105,给字符串复制 c c c次, 1 ≤ c ≤ 40 1\le c\le40 1≤c≤40,每次给出 l l l和 r r r, 1 ≤ l , r ≤ 1 0 18 1\le l,r\le10^{18} 1≤l,r≤1018,复制字符串现有位置的 s [ l ] 、 s [ l + 1 ] ⋯ s [ r − 1 ] 、 s [ r ] s[l]、s[l+1]\cdots s[r-1]、s[r] s[l]、s[l+1]⋯s[r−1]、s[r]到字符串最末尾去。 q q q次询问,每次询问一个 k k k, 1 ≤ k ≤ 1 0 18 1\le k\le10^{18} 1≤k≤1018问字符串中 s [ k ] s[k] s[k]是什么字符。
解题思路:看这个数据范围 l 、 r 、 k l、r、k l、r、k这么大就知道暴力复制字符串是不可能实现的,那么只能智取。我让每次复制都是一个新版本,记录每个版本的字符串总长度,查询的时候根据 k k k就知道是第几个版本即第几次复制的结果了。版本 1 1 1是原字符串。
看样例 1 1 1: m a r k m a r k m a r r k m a r k mark \ \ mark \ \ mar \ \ rkmark mark mark mar rkmark,空格是为了区分版本。以查询的是 10 10 10为例, 10 10 10在第三个版本里的第 2 2 2个位置,根据第三个版本的复制信息 [ 5 , 7 ] [5,7] [5,7],则可以知道该字符映射到第二个版本的第 6 6 6个位置,即 10 10 10变成了 6 6 6,按照这样找下去直到第一个版本即找到原字符。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 4e5 + 10, M = 2 * N;
const int maxN = 2010;
const int mod = 1e9 + 7;
const int INF = (1ll << 31) - 1;
const ll LNF = 1e18;
const double eps = 1e-6;
typedef pair<int, int> PII;
ll sz[N];
struct Node{
ll a, b;
}v[N];
void solve() {
int n, c, q; cin >> n >> c >> q;
for (int i = 0; i <= c + 2; i ++ ) v[i].a = v[i].b = sz[i] = 0;
string s; cin >> s; s = " " + s;
v[1].a = 1, v[1].b = n;
for (int i = 2; i <= c + 1; i ++ ) cin >> v[i].a >> v[i].b;
sz[0] = 0;
sz[1] = n;
for (int i = 2; i <= c + 1; i ++ ) sz[i] = sz[i - 1] + v[i].b - v[i].a + 1;
sz[c + 2] = 9e18;
while (q -- ) {
ll x; cin >> x;
while (1) {
ll ver = 1;
while (sz[ver] < x) ver ++ ;
ll pos = x - sz[ver - 1];//第ver版本中第pos个
if (x <= n) break;
x = v[ver].a + pos - 1;
}
cout << s[x] << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int _;
cin >> _;
//_ = 1;
while (_ -- ) {
solve();
}
return 0;
}
D. Mark and Lightbulbs
题目大意:给定两个长度相同只包含 0 0 0和 1 1 1的 s s s串和 t t t串,你的操作是选择 i ∈ [ 2 , n − 1 ] i\in[2,n-1] i∈[2,n−1]若满足 s i − 1 ≠ s i + 1 s_{i-1}\ne s_{i+1} si−1=si+1,可以将 s i s_i si从 0 0 0变成 1 1 1 或 1 1 1变成 0 0 0。问是否能通过这样的操作把 s s s串变成和 t t t串一样,能就输出最小操作次数,不能输出 − 1 -1 −1
解题思路:因为 s s s第一个位置和最后一个位置不能换,如果和 t t t相应第一个和最后一个位置数不同,直接输出 − 1 -1 −1。再考虑操作。看 00010 00010 00010,正好在 0 0 0和 1 1 1相接触的位置能换,能够把中间第3位给换成 1 1 1变成 00110 00110 00110,再把第2位给换成 1 1 1变成 01110 01110 01110,还能把最右边的 1 1 1往左给依次换成 0 0 0。因此这个 s s s串中操作后 0 0 0和 1 1 1的段数不会变。那么 t t t串中 0 0 0和 1 1 1的段数要和 s s s串相同,只有位置不同。把所有 0 0 0和 1 1 1相接触的位置放入 v e c t o r vector vector里。当 s s s串的这些位置变成和 t t t串一样,那么两个串就相同了(自己思考思考)。
设 s ˉ = ( s 1 ⊕ s 2 ) ( s 2 ⊕ s 3 ) ⋯ ( s n − 1 ⊕ s n ) \bar s=(s_1\oplus s_2)(s_2\oplus s_3)\cdots(s_{n-1}\oplus s_n) sˉ=(s1⊕s2)(s2⊕s3)⋯(sn−1⊕sn)
a 1 , a 2 , ⋯ , a k a_1,a_2,\cdots ,a_k a1,a2,⋯,ak是 s ˉ \bar s sˉ中 1 1 1的位置, b 1 , b 2 , ⋯ , b k b_1,b_2,\cdots ,b_k b1,b2,⋯,bk是 t ˉ \bar t tˉ中 1 1 1的位置
答案就是 ∣ a 1 − b 1 ∣ + ∣ a 2 − b 2 ∣ + ⋯ + ∣ a k − b k ∣ |a_1-b_1|+|a_2-b_2|+\cdots+|a_k-b_k| ∣a1−b1∣+∣a2−b2∣+⋯+∣ak−bk∣
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 4e5 + 10, M = 2 * N;
const int maxN = 2010;
const int mod = 1e9 + 7;
const int INF = (1ll << 31) - 1;
const ll LNF = 1e18;
const double eps = 1e-6;
typedef pair<int, int> PII;
void solve() {
int n; cin >> n;
string a, b; cin >> a >> b; a = " " + a, b = " " + b;
if (a[1] != b[1] || a[n] != b[n]) {
cout << -1 << '\n';
return ;
}
vector<int> pos_s, pos_t;
for (int i = 1; i < n; i ++ ) {
if (a[i] != a[i + 1]) pos_s.push_back(i);
if (b[i] != b[i + 1]) pos_t.push_back(i);
}
if (pos_t.size() != pos_s.size()) cout << -1 << '\n';
else {
ll sum = 0;
for (int i = 0; i < pos_t.size(); i ++ ) sum += abs(pos_s[i] - pos_t[i]);
cout << sum << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int _;
cin >> _;
//_ = 1;
while (_ -- ) {
solve();
}
return 0;
}
E. Mark and Professor Koro
题目大意:给定长度为 n n n的一个序列 a 1 , a 2 , ⋯ , a n a_1,a_2,\cdots,a_n a1,a2,⋯,an,若出现两个相同的元素 x x x,删除他们两,给序列中增加一个 x + 1 x+1 x+1,有 q q q次询问,每次给出 x x x和 y y y,将序列中 a [ x ] a[x] a[x]替换成 y y y。每次询问都要输出现在序列中最大的元素。
解题思路:线段树+二分,线段树中维护每个元素出现的次数,普通的区间修改、区间查询。每个单点只有两个值 0 0 0或 1 1 1,现在查询操作有三步,删除 a [ x ] a[x] a[x]、加入 y y y、查询最大位置的 1 1 1:
-
当删除 a [ x ] a[x] a[x]时:
如果线段树中 a [ x ] a[x] a[x]出现的次数是 1 1 1,直接改成 0 0 0
如果线段树中 a [ x ] a[x] a[x]出现的次数是 0 0 0,删除它会借位,影响右边高位的第一个 1 1 1,这个 1 1 1要变 0 0 0,这个 1 1 1左边低 1 1 1位一直到 a [ x ] a[x] a[x]位全变 1 1 1,用二分去找右边高位第一个 1 1 1的位置,二分详情看代码
-
当增加 y y y时:
如果线段树中 y y y出现的次数是 0 0 0,直接改成 1 1 1
如果线段树中 a [ x ] a[x] a[x]出现的次数是 1 1 1,增加它会进位,影响右边高位的第一个 0 0 0,这个 0 0 0要变 1 1 1,这个 0 0 0左边低 1 1 1位一直到 y y y位全变 0 0 0,用二分找右边高位第一个 1 1 1的位置,二分详情看代码
-
二分出最大位置的 1 1 1,二分详情看代码
参考z巨的代码,我自己写的分块调一天了没出来呜呜呜
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 100, M = 2 * N;
const int maxN = 2010;
const int mod = 1e9 + 7;
const int INF = (1ll << 31) - 1;
const ll LNF = 1e18;
const double eps = 1e-6;
typedef pair<int, int> PII;
int cnt[N], a[N];
struct {
int l, r, sum, lazy;
}tr[N << 2];
void push_up(int root) {
int ch = root << 1;
tr[root].sum = tr[ch].sum + tr[ch + 1].sum;
}
void build(int root, int l, int r) {
tr[root].l = l, tr[root].r = r, tr[root].lazy = -1;
if (l == r) {
tr[root].sum = cnt[l];
return ;
}
int mid = (l + r) >> 1;
int ch = root << 1;
build(ch, l, mid), build(ch + 1, mid + 1, r);
push_up(root);
}
void push_down(int root) {
if (tr[root].lazy != -1) {
int ch = root << 1;
tr[ch].lazy = tr[root].lazy;
tr[ch + 1].lazy = tr[root].lazy;
tr[ch].sum = (tr[ch].r - tr[ch].l + 1) * tr[ch].lazy;
tr[ch + 1].sum = (tr[ch + 1].r - tr[ch + 1].l + 1) * tr[ch + 1].lazy;
tr[root].lazy = -1;
}
}
void change(int root, int L, int R, int c) {
if (L <= tr[root].l && R >= tr[root].r) {
tr[root].sum = (tr[root].r - tr[root].l + 1) * c;
tr[root].lazy = c;
return ;
}
push_down(root);
int mid = (tr[root].l + tr[root].r) >> 1;
int ch = root << 1;
if (L <= mid) change(ch, L, R, c);
if (R > mid) change(ch + 1, L, R, c);
push_up(root);
}
int query(int root, int L, int R) {
if (L <= tr[root].l && R >= tr[root].r) return tr[root].sum;
int ans = 0;
push_down(root);
int mid = (tr[root].l + tr[root].r) >> 1;
int ch = root << 1;
if (L <= mid) ans += query(ch, L, R);
if (R > mid) ans += query(ch + 1, L, R);
return ans;
}
void solve() {
int n, q; cin >> n >> q;
for (int i = 1; i <= n; i ++ ) {
cin >> a[i];
cnt[a[i]] ++ ;
}
int lim = 2e5 + 50;
for (int i = 1; i <= lim; i ++ ) {
cnt[i + 1] += cnt[i] / 2;
cnt[i] %= 2;
}
build(1, 1, lim);
while (q -- ) {
int x, y; cin >> x >> y;//减去a[x],增加y
int t = query(1, a[x], a[x]);
if (t == 1) change(1, a[x], a[x], 0);//1个,直接改成0
else {
//找到a[x]右边第一个1
int l = a[x] + 1, r = lim;
while (l < r) {
int mid = (l + r) >> 1;
if (query(1, a[x] + 1, mid) > 0) r = mid;
else l = mid + 1;
}
change(1, a[x], l - 1, 1);
change(1, l, l, 0);
}
a[x] = y;
t = query(1, a[x], a[x]);
if (t == 0) change(1, a[x], a[x], 1);
else {
//找到a[x]右边第一个0
int l = a[x] + 1, r = lim;
while (l < r) {
int mid = (l + r) >> 1;
if (mid - a[x] > query(1, a[x] + 1, mid)) r = mid;
else l = mid + 1;
}
change(1, a[x], l - 1, 0);
change(1, l, l, 1);
}
int S = query(1, 1, lim);
int l = 1, r = lim;
while (l < r) {
int mid = (l + r) >> 1;
if (query(1, 1, mid) < S) l = mid + 1;
else r = mid;
}
cout << l << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int _;
//cin >> _;
_ = 1;
while (_ -- ) {
solve();
}
return 0;
}