题目地址:P5522 [yLOI2019] 棠梨煎雪 - 洛谷 - 计算机科学教育新生态 ↗
题意#
给定 个长度为 的 串。存在某些位置为 ?
,每个 ?
独立,既可以取 ,也可以是 .
维护 次操作,每次操作可能为:
- 区间查询
- 查询有多少个只包含 的串 满足:存在一种对
?
的赋值方法,使得区间内的每个 串都能与 完全匹配。
- 查询有多少个只包含 的串 满足:存在一种对
- 单点修改
- 把第 个串替换为给定的新串。
.
分析#
区间查询 单点修改 线段树。
主要问题在:如何处理对 进行的查询操作。
若区间中存在两个串 ,存在 使得 和 一个是 ,另一个是 ,那么合法串的个数为 。
否则,只需要知道,有多少个 满足:, ?
.
若有 个满足要求的 ,则区间询问的答案是 .
维护信息#
考虑对 个字符串建立线段树,以编号为下标。
发现 ,可以用 int
类型存储二进制压缩后的的状态。
考虑用线段树: 记录必须为 的位置, 记录必须为 的位置。
具体地,设线段树的 号节点代表的区间为 ,维护下列信息:
- 十进制数 :在 的二进制表示下,第 位为 当且仅当字符串的第 为被确定为了 。
- 十进制数 :在 的二进制表示下,第 位为 当且仅当字符串的第 为被确定为了 。
根据题目性质,线段树对 和 维护区间按位或
和。
也就是说,对于 操作,把左右两端按位或
即可,即:
void push_up(int id) {
c[id].v0 = c[ls(id)].v0 | c[rs(id)].v0;
c[id].v1 = c[ls(id)].v1 | c[rs(id)].v1;
}
cpp询问#
和一般的区间查询一样,但每次要包含两个返回值,与 的含义相同,记录当前查询区间的信息。
还是可以用按位或
合并左右两个区间。
当遇到左右区间产生矛盾时,直接 来代表此次询问无解,向上递归时遇到 就不用合并区间了。
判断无解: 时无解。此时存在一个 满足 必须为 且 必须为 ,矛盾。
pair<int, int> query(int id, int l, int r, int X, int Y) {
if(X <= l && r <= Y) {
if(c[id].v0 & c[id].v1) return make_pair(-1, -1);
else return make_pair(c[id].v0, c[id].v1);
}
int mid = (l + r) >> 1;
pair<int, int> t1 = make_pair(0, 0), t2 = make_pair(0, 0), ret = make_pair(0, 0);
if(X <= mid) t1 = query(ls(id), l, mid, X, Y);
if(Y > mid) t2 = query(rs(id), mid + 1, r, X, Y);
if(t1.first == -1 || t2.first == -1) return make_pair(-1, -1);
ret.first = t1.first | t2.first; ret.second = t1.second | t2.second;
if(ret.first & ret.second) return make_pair(-1, -1);
else return ret;
}
cpp修改#
就是普通的单点修改。
void update(int id, int l, int r, int X, int w0, int w1) {
if(l == X && r == X) {
c[id].v0 = w0; c[id].v1 = w1;
return ;
}
int mid = (l + r) >> 1;
if(X <= mid) update(ls(id), l, mid, X, w0, w1);
else update(rs(id), mid + 1, r, X, w0, w1);
push_up(id);
}
cpp代码#
#include <bits/stdc++.h>
using namespace std;
const int MAXM = 1e5 + 10;
struct Point{
int v0; //v0[i]=1 表示:二进制下的第i位确定为 0
int v1; //v1[i]=1 表示:二进制下的第i位确定为 1
} c[MAXM << 2];
int n, m, q;
int v0[MAXM], v1[MAXM];
int lc[MAXM << 2], rc[MAXM << 2];
namespace Segment_Tree{
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
void push_up(int id) {
c[id].v0 = c[ls(id)].v0 | c[rs(id)].v0;
c[id].v1 = c[ls(id)].v1 | c[rs(id)].v1;
}
void build(int id, int l, int r) {
lc[id] = l; rc[id] = r;
if(l == r) {
c[id] = (Point){v0[l], v1[l]};
return ;
}
int mid = (l + r) >> 1;
build(ls(id), l, mid);
build(rs(id), mid + 1, r);
push_up(id);
}
void update(int id, int l, int r, int X, int w0, int w1) {
if(l == X && r == X) {
c[id].v0 = w0; c[id].v1 = w1;
return ;
}
int mid = (l + r) >> 1;
if(X <= mid) update(ls(id), l, mid, X, w0, w1);
else update(rs(id), mid + 1, r, X, w0, w1);
push_up(id);
}
pair<int, int> query(int id, int l, int r, int X, int Y) {
if(X <= l && r <= Y) {
if(c[id].v0 & c[id].v1) return make_pair(-1, -1);
else return make_pair(c[id].v0, c[id].v1);
}
int mid = (l + r) >> 1;
pair<int, int> t1 = make_pair(0, 0), t2 = make_pair(0, 0), ret = make_pair(0, 0);
if(X <= mid) t1 = query(ls(id), l, mid, X, Y);
if(Y > mid) t2 = query(rs(id), mid + 1, r, X, Y);
if(t1.first == -1 || t2.first == -1) return make_pair(-1, -1);
ret.first = t1.first | t2.first; ret.second = t1.second | t2.second;
if(ret.first & ret.second) return make_pair(-1, -1);
else return ret;
}
};
using namespace Segment_Tree;
string in;
int main() {
scanf("%d%d%d", &n, &m, &q);
for(int i = 1; i <= m; i++) {
cin >> in;
for(int j = 0; j < n; j++) {
v0[i] <<= 1; v1[i] <<= 1;
if(in[j] == '0') v0[i] |= 1;
if(in[j] == '1') v1[i] |= 1;
}
}
build(1, 1, m);
int ans = 0;
while(q--) {
int opt; scanf("%d", &opt);
if(opt == 0) {
int l, r; scanf("%d%d", &l, &r);
pair<int, int> ret = query(1, 1, m, l, r);
if(ret.first == -1) continue;
int tmp = ret.first | ret.second, cnt = 0;
for(int i = 0; i < n; i++) if((tmp & (1 << i)) == 0) cnt++;
ans ^= (1 << cnt);
} else {
int pos, w0 = 0, w1 = 0; cin >> pos >> in;
for(int i = 0; i < n; i++) {
w0 <<= 1; w1 <<= 1;
if(in[i] == '0') w0 |= 1;
if(in[i] == '1') w1 |= 1;
}
update(1, 1, m, pos, w0, w1);
}
}
cout << ans << endl;
return 0;
}
cpp