P4979 矿洞:坍塌

矿洞:坍塌

一、题目描述

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%的数据,N1000,K2000
  • 对于70%的数据,N5000,K5000
  • 对于100%的数据,N500000,K500000,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;
}

三、柯朵莉树解法

:需要O2优化才能过掉,否则会被卡住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;
}
posted @   糖豆爸爸  阅读(37)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2021-08-28 P1029 [NOIP2001 普及组] 最大公约数和最小公倍数问题
2018-08-28 ES优化总结
2017-08-28 使用 URLDecoder 和 URLEncoder 对统一认证中的http地址转义字符进行处理
2013-08-28 为DELL inspiron 14R安装CentOS X64 6.4
Live2D
点击右上角即可分享
微信分享提示