Codeforces Round #807 (Div. 2)(C~E)

C.Mark and His Unfinished Essay

题目大意:给定一个长度为 n n n的字符串, 1 ≤ n ≤ 2 × 1 0 5 1\le n\le2\times10^5 1n2×105,给字符串复制 c c c次, 1 ≤ c ≤ 40 1\le c\le40 1c40,每次给出 l l l r r r 1 ≤ l , r ≤ 1 0 18 1\le l,r\le10^{18} 1l,r1018,复制字符串现有位置的 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[r1]s[r]到字符串最末尾去。 q q q次询问,每次询问一个 k k k 1 ≤ k ≤ 1 0 18 1\le k\le10^{18} 1k1018问字符串中 s [ k ] s[k] s[k]是什么字符。

解题思路:看这个数据范围 l 、 r 、 k l、r、k lrk这么大就知道暴力复制字符串是不可能实现的,那么只能智取。我让每次复制都是一个新版本,记录每个版本的字符串总长度,查询的时候根据 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,n1]若满足 s i − 1 ≠ s i + 1 s_{i-1}\ne s_{i+1} si1=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ˉ=(s1s2)(s2s3)(sn1sn)

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| a1b1+a2b2++akbk

#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

  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的位置,二分详情看代码

  2. 当增加 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的位置,二分详情看代码

  3. 二分出最大位置的 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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值