Limit线段树题单题解(更新中)
P3373 线段树模板 2
\(1 \leq n \leq 10^5\)
题解:考查标记与标记的合并
- 我们考虑打两个懒惰标记实现区间乘和区间加
- 线段树维护区间和
- 对于信息与信息的合并:左儿子加上右儿子即可
- 对于标记与标记的合并:
- 我们发现如果某个区间存在加法的\(lazy\),但是现在对该区间又打上乘法的\(lazy\),如果我们不更新加法的\(lazy\),那么下放加法\(lazy\)的时候会出现错误
- 所以我们在打乘法\(lazy\)的时候也要将加法的\(lazy\)乘上相应的标记值,这是两个不同的标记之间的合并
- 对于相同标记之间,直接合并即可
- 对于信息和标记的合并,简单合并即可
- 标记下放时我们注意,因为加法的\(lazy\)有可能被更新过了,所以我们应该先下放乘法标记并更新区间信息,再下放加法标记并更新区间信息即可
const int N = 1e5 + 10, M = 4e5 + 10;
int n, q, m;
int a[N];
struct info
{
int sum;
};
struct SEG
{
int mul_lazy, add_lazy;
int len;
info val;
SEG()
{
mul_lazy = 1;
add_lazy = 0;
}
} seg[N << 2];
info operator+(const info &a, const info &b)
{
info c;
c.sum = (a.sum + b.sum) % m;
return c;
}
void settag_mul(int id, int tag)
{
seg[id].val.sum = seg[id].val.sum * tag % m;
seg[id].mul_lazy = seg[id].mul_lazy * tag % m;
seg[id].add_lazy = seg[id].add_lazy * tag % m;
}
void settag_add(int id, int tag)
{
seg[id].val.sum = (seg[id].val.sum + seg[id].len * tag % m) % m;
seg[id].add_lazy = (seg[id].add_lazy + tag) % m;
}
void up(int id)
{
seg[id].val = seg[lson].val + seg[rson].val;
}
void down(int id)
{
if (seg[id].mul_lazy != 1)
{
settag_mul(lson, seg[id].mul_lazy);
settag_mul(rson, seg[id].mul_lazy);
}
if (seg[id].add_lazy != 0)
{
settag_add(lson, seg[id].add_lazy);
settag_add(rson, seg[id].add_lazy);
}
seg[id].add_lazy = 0, seg[id].mul_lazy = 1;
}
void build(int id, int l, int r)
{
seg[id].len = r - l + 1;
if (l == r)
{
seg[id].val.sum = a[l];
return;
}
int mid = l + r >> 1;
build(lson, l, mid);
build(rson, mid + 1, r);
up(id);
}
void modify(int id, int l, int r, int ql, int qr, int op, int val)
{
if (ql <= l && r <= qr)
{
if (op == 1)
settag_mul(id, val);
else
settag_add(id, val);
return;
}
down(id);
int mid = l + r >> 1;
if (qr <= mid)
modify(lson, l, mid, ql, qr, op, val);
else if (ql > mid)
modify(rson, mid + 1, r, ql, qr, op, val);
else
{
modify(lson, l, mid, ql, qr, op, val);
modify(rson, mid + 1, r, ql, qr, op, val);
}
up(id);
}
info query(int id, int l, int r, int ql, int qr)
{
if (ql <= l && r <= qr)
{
return seg[id].val;
}
down(id);
int mid = l + r >> 1;
if (qr <= mid)
return query(lson, l, mid, ql, qr);
else if (ql > mid)
return query(rson, mid + 1, r, ql, qr);
else
return query(lson, l, mid, ql, qr) + query(rson, mid + 1, r, ql, qr);
}
void solve()
{
cin >> n >> q >> m;
for (int i = 1; i <= n; ++i)
cin >> a[i];
build(1, 1, n);
while (q--)
{
int op, l, r, x;
cin >> op >> l >> r;
if (op == 1)
{
cin >> x;
modify(1, 1, n, l, r, 1, x);
}
else if (op == 2)
{
cin >> x;
modify(1, 1, n, l, r, 2, x);
}
else
{
cout << query(1, 1, n, l, r).sum % m << endl;
}
}
}
P1276 校门外的树(增强版)
\(1 \leq L \leq 10^4\)
题解:两颗线段树
- 由于懒惰标记较为麻烦,我们考虑建两颗线段树
- 一颗维护树和树苗存在的数量,另一颗维护树存在的数量
- 那么对于答案我们可以通过简单容斥获得
- 最后留下的树苗 = 最后留下的树和树苗的数量 - 最后留下的树的数量
- 每次被砍掉的树苗 = 每次砍掉的树和树苗的数量 - 每次砍掉的树的数量,统计每次数量即可
- 我们考虑线段树如何维护数量
- 线段树维护区间和
- 一开始所有叶子节点全为\(1\)
- 每次砍树,我们将对应的区间的区间和赋值为\(0\),并利用懒惰标记更新下面的区间
- 每次种树,我们将对应的区间的区间和赋值为区间长度,并利用懒惰标记更新下面的区间
const int N = 1e4 + 10, M = 4e5 + 10;
int n, q;
struct SEG_TREE
{
struct info
{
int sum;
// 信息与信息的合并
friend info operator+(const info &a, const info &b)
{
info c;
c.sum = a.sum + b.sum;
return c;
}
};
struct SEG
{
int lazy, len;
info val;
} seg[N << 2];
void settag(int id, int tag)
{
// 信息与标记的合并
if (tag == -1) // 砍树,区间赋0
seg[id].val.sum = 0;
else if (tag == 1) // 种树, 区间赋1
seg[id].val.sum = seg[id].len;
// 标记与标记的合并
seg[id].lazy = tag;
}
void up(int id)
{
seg[id].val = seg[lson].val + seg[rson].val;
}
void down(int id)
{
if (seg[id].lazy == 0)
return;
// 下放标记
settag(lson, seg[id].lazy);
settag(rson, seg[id].lazy);
// 清空标记
seg[id].lazy = 0;
}
void build(int id, int l, int r)
{
seg[id].len = r - l + 1;
if (l == r)
{
seg[id].val.sum = 1;
return;
}
int mid = l + r >> 1;
build(lson, l, mid);
build(rson, mid + 1, r);
up(id);
}
void modify(int id, int l, int r, int ql, int qr, int val)
{
if (ql <= l && r <= qr)
{
settag(id, val);
return;
}
down(id);
int mid = l + r >> 1;
if (qr <= mid)
modify(lson, l, mid, ql, qr, val);
else if (ql > mid)
modify(rson, mid + 1, r, ql, qr, val);
else
{
modify(lson, l, mid, ql, qr, val);
modify(rson, mid + 1, r, ql, qr, val);
}
up(id);
}
info query(int id, int l, int r, int ql, int qr)
{
if (ql <= l && r <= qr)
{
return seg[id].val;
}
down(id);
int mid = l + r >> 1;
if (qr <= mid)
return query(lson, l, mid, ql, qr);
else if (ql > mid)
return query(rson, mid + 1, r, ql, qr);
else
return query(lson, l, mid, ql, qr) + query(rson, mid + 1, r, ql, qr);
}
} tr1, tr2;
void solve()
{
cin >> n >> q;
++n;
tr1.build(1, 1, n), tr2.build(1, 1, n);
int ans = 0;
while (q--)
{
int op, l, r;
cin >> op >> l >> r;
++l, ++r;
if (op == 0)
{
// 被砍掉的树苗和树的总数, 被砍掉的树的总数
int sum1 = tr1.query(1, 1, n, 1, n).sum, sum2 = tr2.query(1, 1, n, 1, n).sum;
tr1.modify(1, 1, n, l, r, -1);
tr2.modify(1, 1, n, l, r, -1);
sum1 -= tr1.query(1, 1, n, 1, n).sum;
sum2 -= tr2.query(1, 1, n, 1, n).sum;
ans += sum1 - sum2;
}
else
{
tr1.modify(1, 1, n, l, r, 1);
}
}
cout << tr1.query(1, 1, n, 1, n).sum - tr2.query(1, 1, n, 1, n).sum << endl;
cout << ans << endl;
}
P5057 [CQOI2006] 简单题
\(1 \leq n \leq 10^5\)
题解:只有标记的线段树
- 线段树不需要维护任何信息,只需要利用标记来完成数字反转
- 对于\(lazy\)来说,\(1\)代表该区间需要反转,\(0\)代表不需要反转
- 因为只有标记,所以我们只需要关注标记与标记的合并:
- 如果某个区间上存在标记,说明该区间需要反转,如果我们再对该区间打上标记的话,该区间不反转,所以标记的合并实际上就是异或
- 对于单点查询来说,如果该区间的\(lazy = 1\),直接输出\(1\)即可,否则输出\(0\)
const int N = 1e5 + 10, M = 4e5 + 10;
int n, q;
struct SEG
{
int lazy;
} seg[N << 2];
void settag(int id, int tag)
{
seg[id].lazy ^= tag;
}
void down(int id)
{
if (seg[id].lazy == 0)
return;
int tag = seg[id].lazy;
settag(lson, tag);
settag(rson, tag);
seg[id].lazy = 0;
}
void modify(int id, int l, int r, int ql, int qr, int tag)
{
if (ql <= l && r <= qr)
{
settag(id, tag);
return;
}
down(id);
int mid = l + r >> 1;
if (qr <= mid)
modify(lson, l, mid, ql, qr, tag);
else if (ql > mid)
modify(rson, mid + 1, r, ql, qr, tag);
else
{
modify(lson, l, mid, ql, qr, tag);
modify(rson, mid + 1, r, ql, qr, tag);
}
}
int query(int id, int l, int r, int x)
{
if (l == r)
return seg[id].lazy;
down(id);
int mid = l + r >> 1;
if (x <= mid)
return query(lson, l, mid, x);
else
return query(rson, mid + 1, r, x);
}
void solve()
{
cin >> n >> q;
while (q--)
{
int op, l, r, x;
cin >> op;
if (op == 1)
{
cin >> l >> r;
modify(1, 1, n, l, r, 1);
}
else
{
cin >> x;
if (query(1, 1, n, x) == 1)
cout << 1 << endl;
else
cout << 0 << endl;
}
}
}
P4588 [TJOI2018] 数学计算
\(1 \leq Q \leq 10^5\)
题解:对时间点建线段树
- 我们考虑对于时间点建线段树
- 线段树维护区间乘,初始所有区间为\(1\)
- 对于操作一,我们直接在当前的时间点上修改即可
- 对于操作二,我们直接在线段树将第\(pos\)次时间点上的修改为\(1\)即可
- 对于查询来说,我们直接输出\(seg[1]\)即可
const int N = 1e5 + 10, M = 4e5 + 10;
int q, m;
int mp[N];
struct info
{
int ans;
};
struct SEG
{
info val;
} seg[N << 2];
info operator+(const info &a, const info &b)
{
info c;
c.ans = a.ans * b.ans % m;
return c;
}
void up(int id)
{
seg[id].val = seg[lson].val + seg[rson].val;
}
void build(int id, int l, int r)
{
if (l == r)
{
seg[id].val.ans = 1;
return;
}
int mid = l + r >> 1;
build(lson, l, mid);
build(rson, mid + 1, r);
up(id);
}
void change(int id, int l, int r, int x, int val)
{
if (l == r)
{
seg[id].val.ans = val;
return;
}
int mid = l + r >> 1;
if (x <= mid)
change(lson, l, mid, x, val);
else
change(rson, mid + 1, r, x, val);
up(id);
}
void solve()
{
cin >> q >> m;
build(1, 1, q);
for (int i = 1; i <= q; ++i)
{
int op, x;
cin >> op >> x;
if (op == 1)
{
change(1, 1, q, i, x);
cout << seg[1].val.ans % m << endl;
}
else
{
change(1, 1, q, x, 1);
cout << seg[1].val.ans % m << endl;
}
}
}
P1908 逆序对
\(1 \leq n \leq 5\times10^5\)
题解:动态开点
const int N = 5e5 + 10, M = 8e6 + 10;
int n, m;
int a[N];
int rt, lson[M], rson[M], sum[M], idx;
void up(int id)
{
sum[id] = sum[lson[id]] + sum[rson[id]];
}
void change(int &id, int l, int r, int x, int val)
{
if (!id)
id = ++idx;
if (l == r)
{
sum[id] += val;
return;
}
int mid = l + r >> 1;
if (x <= mid)
change(lson[id], l, mid, x, val);
else
change(rson[id], mid + 1, r, x, val);
up(id);
}
int query(int id, int l, int r, int ql, int qr)
{
if (!id)
return 0;
if (ql <= l && r <= qr)
{
return sum[id];
}
int mid = l + r >> 1;
if (qr <= mid)
return query(lson[id], l, mid, ql, qr);
else if (ql > mid)
return query(rson[id], mid + 1, r, ql, qr);
else
return query(lson[id], l, mid, ql, qr) + query(rson[id], mid + 1, r, ql, qr);
}
void solve()
{
m = 1e9 + 10;
cin >> n;
for (int i = 1; i <= n; ++i)
cin >> a[i];
long long ans = 0;
for (int i = 1; i <= n; ++i)
{
ans += query(rt, 1, m, a[i] + 1, m);
change(rt, 1, m, a[i], 1);
}
cout << ans << endl;
}