调了一下午,百思不得其解。讨论区数年前有相关的讨论但已经无法查看了。希望有大佬帮忙QAQ
#include <iostream>
#include <cctype>
using namespace std;
const int maxN = 132010;
const int N = 132010;
int tree[maxN << 2]; // 线段树
int ans[maxN << 2]; // 存储最后的集合答案,为 1 表示存在
int tag_set[maxN << 2]; // 赋值的lazy-tag,-1表示不存在
int tag_rev[maxN << 2]; // 翻转的lazy-tag
int ls(int p) { return p << 1; }
int rs(int p) { return p << 1 | 1; }
/// @brief 向上传递区间和,如果区间和等于区间长度,说明这个区间内所有的点都为 1
/// @param p 根节点编号
void push_up(int p)
{
tree[p] = tree[ls(p)] + tree[rs(p)];
}
/// @brief 建树,将tag_set全部变为 -1 以初始化
/// @param p 根节点编号
/// @param pl 根节点对应的左儿子
/// @param pr 根节点对应的右儿子
void build(int p, int pl, int pr)
{
tag_set[p] = -1;
tag_rev[p] = 0;
if (pl == pr)
return;
int mid = (pl + pr) >> 1;
build(ls(p), pl, mid);
build(rs(p), mid + 1, pr);
push_up(p);
}
/// @brief 区间操作,全部置 d。如果翻转标记存在则将其归零
/// @param p 根节点编号
/// @param pl 根节点对应的左儿子
/// @param pr 根节点对应的右儿子
/// @param d 要区间变化的数字,可能为 1 或 0
void addTag_set(int p, int pl, int pr, int d)
{
tag_set[p] = d;
if (d == 1)
tree[p] = pr - pl + 1;
else
tree[p] = 0;
tag_rev[p] = 0;
}
/// @brief 区间操作,反转数字。1变成0,0变成1
/// @param p 根节点编号
/// @param pl 根节点对应的左儿子
/// @param pr 根节点对应的右儿子
void addTag_rev(int p, int pl, int pr)
{
tag_rev[p] ^= 1;
tree[p] = (pr - pl + 1) - tree[p];
}
/// @brief 下放标记,先赋值后翻转
/// @param p 根节点编号
/// @param pl 根节点对应的左儿子
/// @param pr 根节点对应的右儿子
void push_down(int p, int pl, int pr)
{
if (tag_set[p] != -1)
{
int mid = (pl + pr) >> 1;
addTag_set(ls(p), pl, mid, tag_set[p]);
addTag_set(rs(p), mid + 1, pr, tag_set[p]);
tag_set[p] = -1;
}
if (tag_rev[p])
{
int mid = (pl + pr) >> 1;
addTag_rev(ls(p), pl, mid);
addTag_rev(rs(p), mid + 1, pr);
tag_rev[p] = 0;
}
}
/// @brief 以赋值更新区间
/// @param L 区间左端点
/// @param R 区间右端点
/// @param p 根节点编号
/// @param pl 根节点对应的左儿子
/// @param pr 根节点对应的右儿子
/// @param d 赋值
void update_set(int L, int R, int p, int pl, int pr, int d)
{
if (L <= pl && R >= pr)
{
addTag_set(p, pl, pr, d);
return;
}
push_down(p, pl, pr);
int mid = (pl + pr) >> 1;
if (L <= mid)
update_set(L, R, ls(p), pl, mid, d);
if (R > mid)
update_set(L, R, rs(p), mid + 1, pr, d);
push_up(p);
}
/// @brief 翻转区间
/// @param L 区间左端点
/// @param R 区间右端点
/// @param p 根节点编号
/// @param pl 根节点对应的左儿子
/// @param pr 根节点对应的右儿子
void update_rev(int L, int R, int p, int pl, int pr)
{
if (L <= pl && R >= pr)
{
addTag_rev(p, pl, pr);
return;
}
push_down(p, pl, pr);
int mid = (pl + pr) >> 1;
if (L <= mid)
update_rev(L, R, ls(p), pl, mid);
if (R > mid)
update_rev(L, R, rs(p), mid + 1, pr);
push_up(p);
}
/// @brief 自顶向下,根据线段树来更新 ans 数组。如果区间和等于区间长度,则将范围内所有的 ans 置 1
/// @param L 区间左端点
/// @param R 区间右端点
/// @param p 根节点编号
/// @param pl 根节点对应的左儿子
/// @param pr 根节点对应的右儿子
void query(int L, int R, int p, int pl, int pr)
{
push_down(p, pl, pr);
push_up(p);
if (pr - pl + 1 == tree[p])
{
for (int i = pl; i <= pr; i++)
ans[i] = 1;
return;
}
if (pl == pr)
return;
int mid = (pl + pr) >> 1;
if (L <= mid)
query(L, R, ls(p), pl, mid);
if (R > mid)
query(L, R, rs(p), mid + 1, pr);
}
char opt, ch1, ch2, temp; // 操作编号,左括号,右括号,逗号
int x, y; // 数字1, 数字2
int main()
{
build(1, 0, N);
while (cin >> opt >> ch1 >> x >> temp >> y >> ch2)
{
// 将端点加倍以处理开闭区间的情况。若为开区间还需额外增减。
x = x * 2 + (ch1 == '(');
y = y * 2 - (ch2 == ')');
if (opt == 'U') // 并集:T范围内置1
update_set(x, y, 1, 0, N, 1);
else if (opt == 'I') // 交集:T的补集范围内置1
{
update_set(0, x - 1, 1, 0, N, 0);
update_set(y + 1, N, 1, 0, N, 0);
}
else if (opt == 'D') // 减:T范围内置0
update_set(x, y, 1, 0, N, 0);
else if (opt == 'C') // 反减:T范围内翻转再执行I
{
update_rev(0, N, 1, 0, N);
update_set(0, x - 1, 1, 0, N, 0);
update_set(y + 1, N, 1, 0, N, 0);
}
else // 异或:范围内翻转
update_rev(x, y, 1, 0, N);
}
query(0, N, 1, 0, N); // 更新 ans
bool flag = true; // 判断是否应该输出空集
// 在 0 到 N 范围内,将连续的 ans 进行输出
for (int i = 0; i <= N; i++)
{
int x, y;
char ch1, ch2;
if (ans[i] == 1)
{
flag = false;
x = i;
if (x % 2 == 0)
ch1 = '[';
else
ch1 = '(';
int j = i;
while (ans[j] != 0)
j++;
y = j - 1;
if (y % 2 == 0)
ch2 = ']';
else
{
ch2 = ')';
y++;
}
printf("%c%d,%d%c ", ch1, x / 2, y / 2, ch2);
i = j;
}
}
if (flag)
printf("empty set\n");
return 0;
}