刷题周记(三.2)—— #线段树:P3372、P3373 #分块:T170586 数列分块入门1 、T170589 数列分块入门 4

——2021年02月28日(周日)

线段树

一、P3372 【模板】线段树 1

题目链接
第一次写线段树……接近100行的代码(加注释)有点吃不消啊
以后应该会好很多吧?
主要注意一下懒标记得问题。
其他除了有点绕,勉强还行。

#include<bits/stdc++.h>
using namespace std;
const int N = 4e5 + 10;
#define ll long long
int a[N];

ll n, m;
//个人习惯:将这些与树状数组的元素直接挂钩的写在结构体里面
struct T {
	//区间范围,区间中心
	ll l, r, mid;
	//对应属性
	ll sum;
	//懒标记
	ll plz;
	//左右子节点位置:left son position,right son position
	ll lsp, rsp; 
}t[N];

void build(ll i, ll l, ll r){
    t[i].l = l;
    t[i].r = r;
	t[i].mid = (l + r) >> 1;
	t[i].lsp = i << 1;
	t[i].rsp = i << 1 | 1;
	//当前节点是叶子节点(赋值为a[i],结束)
    if(l == r) {
        t[i].sum = a[l];
        return ;
    }
    //不是叶子节点进行以下操作:
    //不断往下面二分原数组
    build(t[i].lsp, l, t[i].mid);
    build(t[i].rsp, t[i].mid + 1, r);
    //求和:两子节点.sum相加
    t[i].sum = (t[t[i].lsp].sum + t[t[i].rsp].sum);
    return ;
}

void addplz(ll i, ll plz) {
	//第一个操作:把父节点的懒标记吃了
	t[i].sum += (t[i].r - t[i].l + 1) * plz;
	//回归定义:懒标记是留给子节点吃的
	//所以要加上来自父节点的懒标记
	t[i].plz += plz; 
}
void pushdown(ll i) {
	if(t[i].plz) {
		//有两个同样的操作,所以用子函数
		addplz(t[i].lsp, t[i].plz);
		addplz(t[i].rsp, t[i].plz);
		//子节点已经吃了,懒标记作用完成,归零
		t[i].plz = 0; 
	}
    return ;
}
void add(ll i, ll l, ll r, ll k) {
	//分四种情况:
	//第一种:线段树区间[ti.l, ti.r]完全不包括目标段
	if(t[i].l > r || t[i].r < l) return ;
	//第二种:线段树区间[ti.l, ti.r]完全包括目标段
	//懒标记:留给子节点吃.因为:遇到一个新的k,父节点先吃,精确到子节点时,子节点再吃,可以类推.
	if(t[i].l >= l && t[i].r <= r) {
		//父节点先吃.(这里的父节点,指代当前区间)
        t[i].sum += ( (t[i].r - t[i].l + 1) * k);
		//懒标记:留给子节点吃.因为:遇到一个新的k,父节点先吃,精确到子节点时,子节点再吃,可以类推.
        t[i].plz = (t[i].plz + k);
        return ;   
	}
	//不是以上情况,说明与子节点有交集,就要调用到子节点了,此时就要将父节点的懒标记下放,确保子节点的值是正确的.
	//注意:这里下放的懒标记不包含k
    pushdown(i);
    if(t[t[i].lsp].r >= l) add(t[i].lsp, l, r, k);
    if(t[t[i].rsp].l <= r) add(t[i].rsp, l, r, k);
    //回溯后,加上两子节点的值
    t[i].sum = (t[t[i].lsp].sum + t[t[i].rsp].sum);
    return ;
}

ll search(ll i,ll l,ll r){
	//这里也是上面四种情况
	//一、完全不包括
    if(t[i].r < l || t[i].l > r)  return 0;
    //二、完全包括
    if(t[i].l >= l && t[i].r <= r) return t[i].sum;
    //精确到子区间找:
	//找之前下放懒标记
    pushdown(i);
	//另外定义一个sum,不要修改.sum 
    ll sum = 0;
    //目标区间与左节点有交集
    if(t[t[i].lsp].r >= l) sum += search(t[i].lsp, l, r);
    //目标区间与右节点有交集
    if(t[t[i].rsp].l <= r) sum += search(t[i].rsp, l, r);
    return sum;
}

int main() {
	cin >> n >> m;
	for(int i = 1; i <= n; i ++) cin >> a[i];
	build(1, 1 ,n);
	while(m --)
	{
		int k; cin >> k;
		if(k == 1) {
			ll x, y, c;
			scanf("%lld%lld%lld", &x, &y, &c);
			add(1, x, y, c);
		}
		else {
			ll x, y;
			scanf("%lld%lld", &x, &y);
			printf("%lld\n", search(1, x, y));
		}
	} 
	return 0;
}

二、P3373 【模板】线段树 2

题目

#include<bits/stdc++.h>
using namespace std;
const int N = 4e6 + 10;
#define ll long long
ll a[N];
ll MOD;
ll n, m;
//我怕麻烦就把所有东西弄在结构体里面了 
struct T {
	ll l, r, mid;
	ll sum;
	ll plz, mlz;
	ll lsp, rsp; 
}t[N];

void build(ll i, ll l, ll r){
    t[i].l = l;
    t[i].r = r;
	t[i].mid = (l + r) >> 1;
	t[i].lsp = i << 1;
	t[i].rsp = i << 1 | 1;
	t[i].mlz = 1;
	//这是叶子节点(把对应a[l]赋值,然后结束) 
    if(l == r) {
        t[i].sum = a[l] % MOD;
        return ;
    }
    //不是叶子的会进行以下操作: 
    //不断二分往下建树 
    build(t[i].lsp, l, t[i].mid);
    build(t[i].rsp, t[i].mid + 1, r);
    //求和:两子节点的和相加。
    t[i].sum = (t[t[i].lsp].sum + t[t[i].rsp].sum) % MOD;
    return ;
}

void addplz(ll i, ll plz, ll mlz) {
	//第1个操作:把懒标记吃了
	t[i].sum = (t[i].sum * mlz + (t[i].r - t[i].l + 1) * plz) % MOD;
	//吃了还要给下一个子节点留着(想想懒标记定义: 给子节点留着的) 
	//所以第2个操作:把父节点给的懒标记 与 自己原有的合起来
	t[i].plz = (t[i].plz * mlz + plz) % MOD;
	t[i].mlz = (t[i].mlz * mlz) % MOD; 
}
void pushdown(ll i) {
		//因为下放会有两个操作,所以用子函数. 
		addplz(t[i].lsp, t[i].plz, t[i].mlz);
		addplz(t[i].rsp, t[i].plz, t[i].mlz);
		//归零\1
		t[i].plz = 0;
		t[i].mlz = 1;
    return ;
}
void add_p(ll i, ll l, ll r, ll k) {
	//分 四种情况:
	//第一:线段树区间[ti.l, ti.r] 完全不包含目标段:
	if(t[i].l > r || t[i].r < l) return ;
	//第一:线段树区间[ti.l, ti.r] 完全 包含目标段:使出 懒标记 大法 
	//懒标记是指:走到当前节点,才把当前节点要加上的数加上,子节点(区间每一个元素)要加的数,先存在父节点的plz 
	if(t[i].l >= l && t[i].r <= r) {
		//这里用到了这整个区间,现在就把plz吃了 
        t[i].sum = (t[i].sum + (t[i].r - t[i].l + 1) * k) % MOD;
        t[i].plz = (t[i].plz + k) % MOD;
        return ;
	}
	//如果都不是,就要下放plz给子节点,否则子节点会遗漏应加上的数 
	//注意:这里给子节点吃的懒标记不包括k,是之前的 
    pushdown(i);
    if(t[t[i].lsp].r >= l) add_p(t[i].lsp, l, r, k);
    if(t[t[i].rsp].l <= r) add_p(t[i].rsp, l, r, k);
    //回溯之后,加上新的子节点的值 
    t[i].sum = (t[t[i].lsp].sum + t[t[i].rsp].sum) % MOD;
    return ;
}
//乘法 
void add_m(ll i, ll l, ll r, ll k) {
	if(t[i].l > r || t[i].r < l) return ;
	if(t[i].l >= l && t[i].r <= r) {
        t[i].sum = (t[i].sum * k) % MOD;
        t[i].mlz = (t[i].mlz * k) % MOD;
        t[i].plz = (t[i].plz * k) % MOD;
        return ;
	}
    pushdown(i);
    if(t[t[i].lsp].r >= l) add_m(t[i].lsp, l, r, k);
    if(t[t[i].rsp].l <= r) add_m(t[i].rsp, l, r, k);
    t[i].sum = (t[t[i].lsp].sum + t[t[i].rsp].sum) % MOD;
    return ;
}

ll search(ll i,ll l,ll r){
	//这里与上面差不多,都是四种情况
	//第一是完全不包括 
    if(t[i].r < l || t[i].l > r)  return 0;
    //第二是完全包括 
    if(t[i].l >= l && t[i].r <= r) return t[i].sum % MOD;
    //然后是还要在子区间里面找
	//找之前先把懒标记下放 
    pushdown(i);
	//定义另一个sum ,不要修改原来的.sum 
    ll sum = 0;
    //目标区间与左子树有交集 
    if(t[t[i].lsp].r >= l) sum += search(t[i].lsp, l, r);
    //目标区间与右子树有交集
    if(t[t[i].rsp].l <= r) sum += search(t[i].rsp, l, r);
    return sum % MOD;
}

int main() {
//	freopen("ttt.in", "r", stdin);
//	freopen("ttt.out", "w", stdout);
	cin >> n >> m >> MOD;
	for(int i = 1; i <= n; i ++) {
		cin >> a[i];
		a[i] %= MOD;
	}
	build(1, 1 ,n);
	for(int i = 1; i <= m; i ++) {
		int k; cin >> k;
		if(k == 1) {
			ll x, y, c;
			c %= MOD;
			cin >> x >> y >> c;
			add_m(1, x, y, c);
		}
		if(k == 2) {
			ll x, y, c;
			c %= MOD;
			cin >> x >> y >> c;
			add_p(1, x, y, c);
		}
		if(k == 3) {
			ll x, y;
			cin >> x >> y;
			cout << search(1, x, y) << endl;
		}
	} 
	return 0;
}

——2021年03月06日(周六)

分块

三、T170586 数列分块入门1

题目:题三、T170586 数列分块入门1

这道题首先是用线段树写的:

#include<bits/stdc++.h>
using namespace std;
#define lsp p << 1 
#define rsp p << 1 | 1
#define ll long long
const int N = 50010;
ll a[4 * N];

struct T {
	int l, r;
	ll num, plz;
	T operator + (const T b) const {
		T c;
		c.plz = 0;
		c.l = l, c.r = b.r;
		c.num = num + b.num;
		return c;
	}
}t[4 * N];

void build(int p, int l, int r) {
	if(l == r) {
		t[p].l = l, t[p].r = r;
		t[p].num = a[l];
		return ;
	}
	int mid = (l + r) >> 1;
	build(lsp, l, mid);
	build(rsp, mid + 1, r);
	t[p] = t[lsp] + t[rsp];
	return ;
}

//子节点
void addplz(int p, ll plz) {
    t[p].num += plz * (t[p].r - t[p].l + 1);
    t[p].plz += plz;
}
void pushdown(int p) {
    addplz(lsp, t[p].plz);
    addplz(rsp, t[p].plz);
    t[p].plz = 0;
}
//整个区间的元素加上k
void add(int p, int l, int r, ll k) {
    //包含在内
    if(l <= t[p].l && t[p].r <= r) {
        t[p].num += k * (t[p].r - t[p].l + 1);
        t[p].plz += k;
        return ;
    }
    if(t[p].plz) pushdown(p);
    if(t[lsp].r >= l) add(lsp, l, r, k);
    if(t[rsp].l <= r) add(rsp, l, r, k);
    t[p] = t[lsp] + t[rsp];
    return ;
}

T search(int p, int pos) {
    if(t[p].l == t[p].r) {
        return t[p];
    }
    if(t[p].plz) pushdown(p);
    if(pos <= t[lsp].r) return search(lsp, pos);
    else return search(rsp, pos);
}
 
int main() {
//	freopen("ttt.in", "r", stdin);
//	freopen("ttt.out", "w", stdout);
	int n;
	scanf("%d", &n);
	for(int i = 1; i <= n; i ++) scanf("%lld", &a[i]);
	build(1, 1, n);
	
	int op, l, r; ll c;
	for(int i = 1; i <= n; i ++) {
		scanf("%d", &op);
		if(op == 0) {
			scanf("%d%d%lld", &l, &r, &c);
			add(1, l, r, c); 
		}
		
		else {
			scanf("%d%d%lld", &l, &r, &c);
			T C = search(1, r);
			printf("%lld\n", C.num);
		}
	}
	return 0;
}

然后是用区块做的

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 500010;
int L[N], R[N], pos[N];
ll a[N], add[N], sum[N];

int n;
int l, r; ll c;

void build() {
    int len = sqrt(n);
    int t = n / len;
    for(int i = 1; i <= t; i ++) {
        L[i] = (i - 1) * len + 1;
        R[i] = i * len;
    }
    if(R[t] < n) t ++, L[t] = (t - 1) * len + 1, R[t] = n;
    for(int i = 1; i <= t; i ++) {
        for(int j = L[i]; j <= R[i]; j ++) {
            pos[j] = i;
            sum[i] += a[j];
        }
    }
}

void change(int l, int r, ll d) {
    int q = pos[l], p = pos[r];
    if(q == p) {
        for(int i = l; i <= r; i ++) a[i] += d;
        sum[q] += (r - l + 1) * d;
        return ;
    }
    for(int i = q + 1; i <= p - 1; i ++) add[i] += d;
    for(int i = l; i <= R[q]; i ++) a[i] += d;
    sum[q] += (R[q] - l + 1) * d;
    for(int i = L[p]; i <= r; i ++) a[i] += d;
    sum[p] += (r - L[p] + 1) * d;
}

ll search (int p) {
    return a[p] + add[pos[p]];
}

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++) scanf("%lld", &a[i]);
    build();
    
    for(int i = 1; i <= n; i ++) {
        int op; scanf("%d", &op);
        scanf("%d%d%lld", &l, &r, &c);
        //询问a[i]
        if(!op) {
            change(l, r, c);
        }
        //区间加上某个值
        else {
            printf("%lld\n", search(r) );
        }
    }
    return 0;
}

四、T170589 数列分块入门 4

题目:T170589 数列分块入门 4

调了三个小时:
错误一:q,pl,r混用
错误二:漏了处理最后一个分组
错误三:欣赏一下,错误时是这么写的:

for(int i = q + 1; i <= p - 1; i ++) {
		sum[i] += (R[i] - L[i] + 1) * d;
		plz[i] += d;
	}
#include<bits/stdc++.h>
using namespace std;
const int N = 500010;
typedef long long ll;
int n;
int L[N], R[N], pos[N];
ll a[N], sum[N], plz[N];
int len;
void build() {
	len = sqrt(n);
	int t = ceil((double)n / len);
	for(int i = 1; i <= t; i ++) {
		L[i] = (i - 1) * len + 1;
		R[i] = len * i;	
	}
	if(R[t] < n) t += 1, L[t] = R[t - 1] + 1, R[t] = n;//错误二事故发生处
	for(int i = 1; i <= t; i ++)
		for(int j = L[i]; j <= R[i]; j ++) {
			pos[j] = i;
			sum[i] += a[j];
		}
	return ;
}

void add(int l, int r, ll d) {
	int q = pos[l], p = pos[r];
	if(q == p) {
		for(int i = l; i <= r; i ++) a[i] += d;//错误一事故发生处
		sum[q] += (r - l + 1) * d;
		return ;
	}
	for(int i = q + 1; i <= p - 1; i ++) plz[i] += d;//错误三事故发生处
	for(int i = l; i <= R[q]; i ++) a[i] += d;
	sum[q] += (R[q] - l + 1) * d;
	for(int i = L[p]; i <= r; i ++) a[i] += d;
	sum[p] += (r - L[p] + 1) * d;
	return ;
}

ll search(int l, int r, ll M) {
	int q = pos[l], p = pos[r]; ll ans = 0;
	if(q == p) {
		for(int i = l; i <= r; i ++) ans += a[i], ans %= M;//错误一事故发生处
		ans += (r - l + 1) * plz[q], ans %= M;
		return ans % M;
	}
	for(int i = q + 1; i <= p - 1; i ++) ans += sum[i] + (R[i] - L[i] + 1) * plz[i], ans %= M;
	for(int i = l; i <= R[q]; i ++) ans += a[i], ans %= M;
	ans += (R[q] - l + 1) * plz[q], ans %= M;
	for(int i = L[p]; i <= r; i ++) ans += a[i], ans %= M;
	ans += (r - L[p] + 1) * plz[p], ans %= M;
	return ans % M;
}

int main() {
	scanf("%d", &n);
	for(int i = 1; i <= n; i ++) scanf("%lld", &a[i]);
	build();
	for(int i = 1; i <= n; i ++) {
		int op; scanf("%d", &op);
		int l, r; ll c;
		scanf("%d%d%lld", &l, &r, &c);
		if(op == 0) add(l, r, c);
		else printf("%lld\n", search(l, r, c + 1));
	}
	return 0;
}

To be continue……

下一篇:点这里

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值