注意事项:
1. dfs序上线段树
2. 当是区间覆盖的时候,要记得判定pushdown能不能down,可能会造成错误的初始值覆盖
3. 在想维护哪些信息的时候,只需要考虑在已经这些清晰的情况下,如何递推计算这些信息以及维护标记就行了,不要绕进去
4. 结构体中有多个变量的时候,索引赋值更不容易出错
一个简单的整数问题
// 板子题目,维护区间加法的和
#include
using namespace std;
const int N = 1e5+10;
typedef long long LL;
// 标签在当前节点已经发生作用了,但是还没有对儿孙发生作用
int w[N];
struct node{
int l, r;
LL sum;
LL add;
}tr[4*N];
void pushup(int u){
tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum;
}
// pusdown主要考虑标签融合,标签向下传递的计算两个方面
void pushdown(node &F, node &L, node &R){
// 标签融合
L.add += F.add;
R.add += F.add;
// 标签向下传递
L.sum = L.sum + (LL)(L.r - L.l + 1) * F.add;
R.sum = R.sum + (LL)(R.r - R.l + 1) * F.add;
// 标签清空
F.add = 0;
}
void pushdown(int u){
pushdown(tr[u], tr[u<<1], tr[u<<1|1]);
}
void build(int u, int l, int r){
if(l == r){
tr[u] = {l, r, w[l], 0};
}
else{
tr[u] = {l, r, 0, 0};
int mid = l + r >> 1;
build(u<<1, l, mid), build(u<<1|1, mid+1, r);
pushup(u);
}
}
// pushdown都是下面的分支里面才有,这样能保证pushdown不越界
void modify(int u, int l, int r, int v){
if(tr[u].l >= l && tr[u].r <= r) {
// 这里修改的时候,标签要发挥作用
tr[u].add += v;
tr[u].sum += (tr[u].r - tr[u].l + 1) * v;
}
else{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u<<1, l, r, v);
if(r > mid) modify(u<<1|1, l, r, v);
pushup(u);
}
}
LL query(int u, int l, int r){
if(tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
else{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l > mid) return query(u<<1|1, l, r);
else if(r <= mid) return query(u<<1, l, r);
else return query(u<<1, l, r) + query(u<<1|1, l, r);
}
}
int main(){
int n, m;
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d", &w[i]);
build(1, 1, n);
while(m--){
char op[2]; int l, r, v;
scanf("%s", op);
if(op[0] == 'Q') scanf("%d %d", &l, &r), printf("%lld\n", query(1, l, r));
else scanf("%d %d %d", &l, &r, &v), modify(1, l, r, v);
}
return 0;
}
市长海报
题目:给定n个区间l-r,依次覆盖,问露出来的有多少个
存在的问题
1. 区间覆盖问题要离散的话时候可能会先问题,需要在离散化的时候加入l+1,r+1,这样才能正确区分
2. 该问题前面都是区间修改,后面都是单点查询,所以他就不需要pushup(),
直接在最后进行暴力的单点查询就可以了,而且也不需要维护区间的信息,只要维护好单点的信息就可以了
3. 如果是不需要离散化的区间覆盖呢,那么可以这么转化[l, l]代表[l, l+1]的颜色,那么覆盖[l, r],其实就是覆盖[l, r-1]
#include
using namespace std;
typedef long long LL;
typedef pair PII;
const int N = 4e4+10;
vector pos;
PII seg[N];
// 标签在当前节点已经发生作用了,但是还没有对儿孙发生作用
int w[N];
struct node{
int l, r;
int type;
int label;
}tr[4*N];
void pushdown(node &F, node &L, node &R){
// 标签融合
if(F.label == 0) return ;
L.label = F.label;
R.label = F.label;
// 标签向下传递
L.type = F.label;
R.type = F.label;
// 标签清空
F.label = 0;
}
void pushdown(int u){
pushdown(tr[u], tr[u<<1], tr[u<<1|1]);
}
void build(int u, int l, int r){
if(l == r){
tr[u] = {l, r, 0, 0};
}
else{
tr[u] = {l, r, 0, 0};
int mid = l + r >> 1;
build(u<<1, l, mid), build(u<<1|1, mid+1, r);
}
}
void modify(int u, int l, int r, int v){
if(tr[u].l >= l && tr[u].r <= r) {
tr[u].label = v;
tr[u].type = v;
}
else{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u<<1, l, r, v);
if(r > mid) modify(u<<1|1, l, r, v);
}
}
int query(int u, int l, int r){
if(tr[u].l >= l && tr[u].r <= r) return tr[u].type;
else{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l > mid) return query(u<<1|1, l, r);
else if(r <= mid) return query(u<<1, l, r);
else return query(u<<1, l, r) + query(u<<1|1, l, r);
}
}
int get(int x){
int l = 0, r = pos.size()-1;
while(l < r){
int mid = l + r + 1>> 1;
if(pos[mid] <= x) l = mid;
else r = mid - 1;
}
return l + 1;
}
int main(){
int T;
cin >> T;
while(T--){
int n; scanf("%d", &n);
pos.clear();
for(int i = 1; i <= n; i++){
int l, r; scanf("%d %d", &l, &r);
seg[i] = {l, r};
pos.push_back(l), pos.push_back(r);
pos.push_back(l+1), pos.push_back(r+1);
}
sort(pos.begin(), pos.end());
pos.erase(unique(pos.begin(), pos.end()), pos.end());
build(1, 1, pos.size());
for(int i = 1; i <= n; i ++){
int l = get(seg[i].first), r = get(seg[i].second);
modify(1, l, r, i);
}
// cout << 1 << endl;
set S;
S.insert(0);
for(int i = 0; i < pos.size(); i++){
int x = get(pos[i]);
// cout << query(1, x, x ) << endl;
S.insert(query(1, x, x));
}
printf("%d\n", S.size()-1);
}
return 0;
}
你能回答这些问题吗
题目链接:https://www.acwing.com/problem/content/description/4347/
问题:
1.一道带暴力成分的线段树
2.n的和最多是1e6个,数据保证单组测试数据中ei的和不会超过2^63,发现2^63次方开个七八次根号就是1了,所以可以不打标记,
直接暴力修改到叶子,然后加一个剪枝就可以了,具体看代码
3. 时间复杂度,每个叶子最多被修改6,7次,按7次算的话,包含某个叶子权值的线段树节点,就是从这个叶子,不断地向上找父亲,
大概也就20个左右,这些就是将一个叶子的权值变成1最多要修改的次数7*20,最后再乘上叶子的总个数,也就n的和,是小于2e8
#include
using namespace std;
typedef long long LL;
const int N = 1e5+10;
LL w[N];
struct node{
int l, r;
LL sum;
}tr[4*N];
void pushup(int u){
tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum;
}
void build(int u, int l, int r){
if(l == r){
tr[u] = {l, r, w[l]};
}
else{
tr[u] = {l, r, 0};
int mid = l + r >> 1;
build(u<<1, l, mid), build(u<<1|1, mid+1, r);
pushup(u);
}
}
void modify(int u, int l, int r){
// 如果区间长度等于区间和的话,说明该区间都是1了,就不用递归开根号了
if(tr[u].r - tr[u].l + 1 == tr[u].sum) return ;
// 递归到叶子,直接对叶子修改
if(tr[u].l >= l && tr[u].r <= r && tr[u].l == tr[u].r) {
tr[u].sum = sqrt(tr[u].sum);
}
else{
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u<<1, l, r);
if(r > mid) modify(u<<1|1, l, r);
pushup(u);
}
}
LL query(int u, int l, int r){
if(tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
else{
int mid = tr[u].l + tr[u].r >> 1;
if(l > mid) return query(u<<1|1, l, r);
else if(r <= mid) return query(u<<1, l, r);
else return query(u<<1, l, r) + query(u<<1|1, l, r);
}
}
int main(){
int C = 0, n;
while(cin >> n){
C++;
printf("Case #%d:\n", C);
for(int i = 1; i <= n; i++) scanf("%lld", &w[i]);
build(1, 1, n);
int m; scanf("%d", &m);
while(m --){
int op, l, r; scanf("%d %d %d", &op, &l, &r);
if(l > r) swap(l, r);
if(op == 0){
modify(1, l, r);
}
else if(op == 1){
printf("%lld\n", query(1, l, r));
}
}
puts("");
}
return 0;
}
地道战
问题:只有01两种元素的数组,动态单点修改,询问包含某个节点的最大连续1的长度
1.维护区间左端最大值,右端最大值,和整体的最大值,pushdown其实是比较好实现的
2.区间查询感觉比较困难,
为啥那么写查询是O(logn)的,而不会退化到O(n),每次递归的时候只会走一条分支,否则直接返回答案,所以
是log(n)(一共只有log层吧)
另一个就是跨区间的时候,可以直接返回答案,感觉比较难想
#include
using namespace std;
typedef long long LL;
const int N = 50010;
struct node{
int l, r;
int lmax, rmax, maxn;
}tr[4*N];
void pushup(int u){
node & F = tr[u], L = tr[u<<1], R = tr[u<<1|1];
F.maxn = max({L.maxn, R.maxn, L.rmax + R.lmax});
F.lmax = L.lmax;
F.rmax = R.rmax;
if(L.maxn == L.r - L.l + 1) F.lmax = max(F.lmax, L.maxn + R.lmax);
if(R.maxn == R.r - R.l + 1) F.rmax = max(F.rmax, R.maxn + L.rmax);
}
void build(int u, int l, int r){
if(l == r){
tr[u] = {l, r, 1, 1, 1};
}
else{
tr[u] = {l, r, 1, 1, 1};
int mid = l + r >> 1;
build(u<<1, l, mid), build(u<<1|1, mid+1, r);
pushup(u);
}
}
void modify(int u, int x, int v){
if(tr[u].l == x && tr[u].r == x){
tr[u].lmax = tr[u].rmax = tr[u].maxn = v;
}
else{
int mid = tr[u].l + tr[u].r >> 1;
if(x <= mid) modify(u<<1, x, v);
else modify(u<<1|1, x, v);
pushup(u);
}
}
int query(int u, int x){
if(tr[u].maxn == (tr[u].r - tr[u].l + 1) || tr[u].l == tr[u].r) return tr[u].maxn;
int mid = tr[u].l + tr[u].r >> 1;
if(x <= mid){
if(mid - tr[u<<1].rmax + 1<= x) return tr[u<<1].rmax + tr[u<<1|1].lmax;
else return query(u<<1, x);
}
else{
if(mid + 1 + tr[u<<1|1].lmax - 1 >= x) return tr[u<<1].rmax + tr[u<<1|1].lmax;
else return query(u<<1|1, x);
}
}
int main(){
int n, m;
while(cin >> n >> m){
build(1, 1, n);
stack stk;
while(m--){
char op[2]; int x;
scanf("%s", op);
if(op[0] == 'D'){
scanf("%d", &x);
stk.push(x);
modify(1, x, 0);
}
else if(op[0] == 'Q'){
scanf("%d", &x);
printf("%d\n", query(1, x));
}
else{
if(stk.size() == 0) continue;
int top = stk.top(); stk.pop();
modify(1, top, 1);
}
}
}
return 0;
}
花瓶与鲜花
一道线段树上的二分,之前也写过一道线段树二分,但是这道和之前的感觉不太一样
之前那个直接query那里二分就可以了,这道题query部分还是正常的,又单独写了一个二分
另外一个关键点是,如何二分x之后的第一个和最后一个,以及如何判断是否不可能
首先可以query出来x之前有多少个空位,这样的话x及其之后第一个空位就是前面+1了,最后一个就算前面+y了
但是如何判断不可能呢,最后一个的计算,我们直接和tr[1]取个min这样的话,算出来的最多也就是最后一个
前面的边界的话,我们不管,这样如果有解的话,他就能算出来一个解,没有解的话他就自动二分道最后一个值了
例外需要注意的是二分里面不能继续缩短范围,不然的话,上面对l的计算可能就不正确了
#include
using namespace std;
typedef long long LL;
const int N = 5e4+10;
struct node{
int l, r;
int sum;
int add;
}tr[4*N];
void pushup(int u){
tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum;
}
void pushdown(node &F, node &L, node &R){
if(F.add == -1) return ;
// 标签融合
L.add = F.add;
R.add = F.add;
// 标签向下传递
L.sum = (L.r - L.l + 1) * F.add;
R.sum = (R.r - R.l + 1) * F.add;
// 标签清空
F.add = -1;
}
void pushdown(int u){
pushdown(tr[u], tr[u<<1], tr[u<<1|1]);
}
void build(int u, int l, int r){
if(l == r){
tr[u] = {l, r, 1, -1};
}
else{
tr[u] = {l, r, r - l + 1, -1};
int mid = l + r >> 1;
build(u<<1, l, mid), build(u<<1|1, mid+1, r);
}
}
void modify(int u, int l, int r, int v){
if(tr[u].l >= l && tr[u].r <= r) {
tr[u].add = v;
tr[u].sum = v * (tr[u].r - tr[u].l + 1);
}
else{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u<<1, l, r, v);
if(r > mid) modify(u<<1|1, l, r, v);
pushup(u);
}
}
int query(int u, int l, int r){
if(tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
else{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l > mid) return query(u<<1|1, l, r);
else if(r <= mid) return query(u<<1, l, r);
else return query(u<<1, l, r) + query(u<<1|1, l, r);
}
}
int find(int u, int x){
if(tr[u].l == tr[u].r) return tr[u].l;
pushdown(u);
if(tr[u<<1].sum >= x ) return find(u<<1, x);
else return find(u<<1|1, x - tr[u<<1].sum);
}
int main(){
int T;
scanf("%d", &T);
while(T--){
int n, m; scanf("%d %d", &n, &m);
build(1, 0, n-1);
while(m--){
int op, x, y; scanf("%d %d %d", &op, &x, &y);
if(op == 1){
int lsum = 0;
if(x - 1 >= 0) lsum = query(1, 0, x - 1);
int L = find(1, lsum + 1);
int R = find(1, min(lsum + y, tr[1].sum));
modify(1, L, R, 0);
if( L > R ) printf("Can not put any one.\n");
else printf("%d %d\n", L, R);
}
else if(op == 2){
int pre = query(1, x, y);
modify(1, x, y, 1);
printf("%d\n", y - x + 1 - pre);
}
}
puts("");
}
return 0;
}