P4979 矿洞:坍塌
矿洞:坍塌
一、题目描述
家的矿塌了之后,就没有经济来源了(不要问我怎么没有存款)。
于是,迫切地想要修复他家的矿。
家的矿共出产三种矿石,所以我们也只能用三种材料来修复他们家的矿。
我们已知共有吨材料,每吨材料均为三种材料中的一种,它们连成了一个串,如:
家对材料的要求非常严格,他每次会选择一段连续区间的材料作为修复的材料。因为不合要求的材料会使得矿再次塌陷,砸死,所以这个连续区间的材料必须满足一下个要求:
- 这段连续区间必须是同一种材料
- 这段连续区间的前一个材料与后一个材料必须不相同。
例如,有一段材料为,则~ 区间的 和 ~ 区间的 均符合要求,而 ~ 区间的 不符合要求。
材料有灵性,所以材料会有变化。
现在有吨材料,个询问。每个询问是以下的种形式之一:
- 表示替换材料,将到区间内的材料替换为,为三种材料字符中的一个。
- 表示是否询问,即询问到区间内的材料是否合法,合法输出,不合法输出。
注意:当或时,你的程序不需要判断前后的情况,而只需要判断区间内的情况.
输入格式
- 第一行一个正整数
- 接下来个字符,表示材料
- 接下来个询问,格式为上述的一种
输出格式
对于每个 B x y 的询问,输出 或
样例输入 #1
15
AACBBABBBCCCBBB
3
B 4 5
B 5 5
B 10 12
样例输出 #1
Yes
Yes
No
提示
- 对于%的数据,
- 对于%的数据,
- 对于%的数据,
二、线段树解法
- 每个字母赋一个不同的值,范围要大。
- 维护区间和
#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;
}
三、柯朵莉树解法
注:需要优化才能过掉,否则会被卡住个测试点
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有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