You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

9.5 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

矿洞:坍塌

一、题目描述

CYJian家的矿塌了之后,就没有经济来源了(不要问我怎么没有存款)。

于是,CYJian迫切地想要修复他家的矿。

CYJian家的矿共出产A,B,C三种矿石,所以我们也只能用A,B,C三种材料来修复他们家的矿。

我们已知共有N吨材料,每吨材料均为A,B,C三种材料中的一种,它们连成了一个串,如:

ABCBCABCBACBCBAA

CYJian家对材料的要求非常严格,他每次会选择一段连续区间的材料作为修复的材料。因为不合要求的材料会使得矿再次塌陷,砸死CYJian,所以这个连续区间的材料必须满足一下2个要求:

  • 这段连续区间必须是同一种材料
  • 这段连续区间的前一个材料与后一个材料必须不相同。

例如,有一段材料为AACBBABBBCCCBBB,则(4~5) 区间的 BB(5~5) 区间的 B 均符合要求,而 (10~12) 区间的 CCC 不符合要求。

材料有灵性,所以材料会有变化。

现在有N吨材料,K个询问。每个询问是以下的2种形式之一:

  • A x y op 表示替换材料,将xy(1<=x<=y<=N)区间内的材料替换为opopA,B,C三种材料字符中的一个。
  • B x y 表示是否询问,即询问xy(1<=x<=y<=N)区间内的材料是否合法,合法输出Yes,不合法输出No

注意:当x=1y=N时,你的程序不需要判断前后的情况,而只需要判断区间内的情况.

输入格式

  • 第一行一个正整数N
  • 接下来N个字符,表示材料
  • 接下来K个询问,格式为上述的一种

输出格式

对于每个 B x y 的询问,输出 YesNo

样例输入 #1

15
AACBBABBBCCCBBB
3
B 4 5
B 5 5
B 10 12

样例输出 #1

Yes
Yes
No

提示

  • 对于30%的数据,N\le1000,K\le2000
  • 对于70%的数据,N\le5000,K\le5000
  • 对于100%的数据,N\le500000,K\le500000,1<x<=y<N

二、线段树解法

  • 每个字母赋一个不同的值,范围要大。
  • 维护区间和
#include <bits/stdc++.h>
const int N = 1000010;
using namespace std;

// 线段树模板
#define int long long
#define ls (u << 1)
#define rs (u << 1 | 1)
#define mid ((l + r) >> 1)

struct Node {
    int l, r, len;
    int sum, tag;
} tr[N];
int a[N];
int n, m;

void pushup(int u) {
    tr[u].sum = tr[ls].sum + tr[rs].sum;
}

void build(int u, int l, int r) {
    tr[u].l = l, tr[u].r = r, tr[u].len = r - l + 1;
    if (l == r) {
        tr[u].sum = a[l]; // 初始化线段树
        return;
    }
    build(ls, l, mid);
    build(rs, mid + 1, r);
    pushup(u);
}

// 向下推送懒标记
void pushdown(int u) {
    if (tr[u].tag) {
        tr[ls].tag = tr[rs].tag = tr[u].tag;
        tr[ls].sum = tr[ls].len * tr[u].tag;
        tr[rs].sum = tr[rs].len * tr[u].tag;
        tr[u].tag = 0; // 向下传递懒标记完毕将父节点的懒标记修改为0
    }
}

// 整体区间赋值为v
void modify(int u, int L, int R, int v) { // 整体区间赋值为v
    int l = tr[u].l, r = tr[u].r;
    if (l > R || r < L) return;
    if (l >= L && r <= R) {
        tr[u].sum = tr[u].len * v; // sum和=区间长度*v
        tr[u].tag = v;             // 懒标记=v
        return;                    // 不再继续向下传递
    }
    pushdown(u); // 如果存在懒标记,在分裂前就下传懒标记
    modify(ls, L, R, v), modify(rs, L, R, v);
    pushup(u);
}
// 区间查询
int query(int u, int L, int R) {
    int l = tr[u].l, r = tr[u].r;
    if (l > R || r < L) return 0;
    if (l >= L && r <= R) return tr[u].sum;
    pushdown(u);
    return query(rs, L, R) + query(ls, L, R);
}

// 将字符映射成数字
int get(char op) {
    if (op == 'A') return op * 1234;
    if (op == 'B') return op * 12345;
    if (op == 'C') return op * 123456;
}

/*[l,r]之间是不是全是A、B、C
 方法整数HASH我给起的名字
 步骤1、A=1234 B=12345 C=123456
 2、如果区间内全是A则区间长度(r-l+1)*1234=sum
 3、如果区间内全是B则区间长度(r-l+1)*12345=sum
 4、如果区间内全是C则区间长度(r-l+1)*123456=sum
*/
bool check(int sum, char c, int l, int r) {
    if (c == 'A') return sum == c * 1234 * (r - l + 1);
    if (c == 'B') return sum == c * 12345 * (r - l + 1);
    if (c == 'C') return sum == c * 123456 * (r - l + 1);
}

signed main() {
#ifndef ONLINE_JUDGE
    freopen("P4979.in", "r", stdin);
#endif
    // 加快读入
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i++) {
        char op;
        cin >> op;
        a[i] = get(op);
    }
    build(1, 1, n);

    int l, r;
    cin >> m;
    while (m--) {
        char op;
        cin >> op;
        if (op == 'A') { // 区间替换操作
            char x;
            cin >> l >> r;
            cin >> x; // 替换成啥
            modify(1, l, r, get(x));
        } else {
            cin >> l >> r;
            int sum = query(1, l, r);
            // 区间内是不是都是一样的字符
            if ((!check(sum, 'A', l, r) && !check(sum, 'B', l, r) && !check(sum, 'C', l, r))) {
                puts("No");
                continue;
            }
            // l=1时没有前面的内容;r=n时没有后面的内容这两种情况不需要检查是不是前后一致只需要检查区间内是不是都是一样的即可
            if (l == 1 || r == n) {
                puts("Yes");
                continue;
            }
            // 执行到这里,肯定是区间内都是一样的,并且l>1 && r<n
            if (query(1, l - 1, l - 1) != query(1, r + 1, r + 1))
                puts("Yes");
            else
                puts("No");
        }
    }
    return 0;
}

三、柯朵莉树解法

:需要O^2优化才能过掉,否则会被卡住3个测试点

#include <bits/stdc++.h>
using namespace std;

// 柯朵莉树模板
struct Node {
    int l, r, v;
    bool operator<(const Node &b) const {
        return l < b.l;
    }
};
set<Node> s;
set<Node>::iterator split(int x) {
    auto it = s.lower_bound({x});
    if (it != s.end() && it->l == x) return it;
    --it;
    int L = it->l, R = it->r, V = it->v;
    s.erase(it);
    s.insert({L, x - 1, V});
    return s.insert({x, R, V}).first;
}

void assign(int l, int r, int v) {
    auto R = split(r + 1), L = split(l);
    s.erase(L, R);
    s.insert({l, r, v});
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("P4979.in", "r", stdin);
#endif
    // 加快读入
    ios::sync_with_stdio(false), cin.tie(0);
    int n;
    cin >> n;   // n个字符表示材料
    int st = 1; // 区间起点start
    char x;
    cin >> x;
    int a = x; // a:当前字符
    int b = a; // b:当前的前一个字符
    for (int i = 2; i <= n; i++) {
        cin >> x;
        a = x;                        // 读入一个字符
        if (a != b) {                 // 如果两个字符不一致,说明开始的新的区间
            s.insert({st, i - 1, b}); // 记录上一个区间
            st = i;                   // 记录新的区间起点
        }
        b = a; // 修改前一个字符
    }
    s.insert({st, n, a}); // 将尾巴打扫干净

    int m; // m次操作
    cin >> m;
    while (m--) {
        cin >> x;
        int l, r;
        cin >> l >> r;
        if (x == 'A') { // 替换材料
            char op;
            cin >> op;
            assign(l, r, op); // 区间平推op
        } else {              // 询问[l,r]区间内的材料是否合法
            auto R = split(r + 1), L = split(l);
            /*
            此时我们想进行一次推平操作,把[2,8]区间内的元素都改成666.首先我们发现,[8,10]是一个区间,
            那么需要先split(9),把[8,8]和[9,10]拆成两个区间。
            同理,原来的[1,2]区间,也需要拆成[1,1]和[2,2]。
            */
            // 注意:当x=1或y=N时,你的程序不需要判断前后的情况,而只需要判断区间内的情况.
            if (l > 1 && r < n) {
                // 之所以要判断是不是l>1,是因为下面需要检查前面的区间块而l=1时是没有前面的区间块的
                // 同理r < n,是因为r=n时是没有后面的区间块的
                L--;                // 前面的区间块,迭代器只能加加减减,有借有还
                if (L->v == R->v) { // 如果前面区间的字符与后面区间的字符一样根据题意输出NO
                    puts("No");
                    continue;
                }
                L++; // 还原迭代器到原始L区间上,迭代器只能加加减减,有借有还
            }

            // 检查区间内的值是不是一致L未能如期到达R输出NO
            auto t = L;
            for (; L != R; L++) {
                if (L->v != t->v)
                    break;
            }
            if (L != R)
                puts("No");
            else
                puts("Yes");

            // 检查是同一个区域的进行整理,合并,起到优化作用不加上会TLE三个点
            assign(t->l, (--L)->r, t->v); // --L是最后一个匹配位置合并[t,--L]
        }
    }
    return 0;
}