## [矿洞:坍塌](https://www.luogu.com.cn/problem/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$ 表示替换材料,将$x$到$y(1<=x<=y<=N)$区间内的材料替换为$op$,$op$为$A,B,C$三种材料字符中的一个。 - $B$ $x$ $y$ 表示是否询问,即询问$x$到$y(1<=x<=y<=N)$区间内的材料是否合法,合法输出$Yes$,不合法输出$No$。 注意:当$x=1$或$y=N$时,你的程序不需要判断前后的情况,而只需要判断区间内的情况. #### 输入格式 - 第一行一个正整数$N$ - 接下来$N$个字符,表示材料 - 接下来$K$个询问,格式为上述的一种 #### 输出格式 对于每个 B x y 的询问,输出 $Yes$ 或 $No$ #### 样例输入 #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 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 using namespace std; // 柯朵莉树模板 struct Node { int l, r, v; bool operator<(const Node &b) const { return l < b.l; } }; set s; set::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; } ```