H - Skyscraper(树状数组、差分、思维、区间加、单点修改)

本文探讨了一种简化方法,通过维护正数序列和差分数组,仅使用单点操作解决H-Skyscraper问题。作者展示了两种不同的代码实现,一种使用了树状数组,另一种则更进一步,只保留必要的数据结构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


H - Skyscraper
题意: 原数组 h n h_n hn初始值均为0,给定数组 a n a_n an是要求达到的值。现在可以选择下标 [ l , r ] [l,r] [l,r]进行区间 + 1 +1 +1的操作。
现在有两种操作:

  1. 1    l    r    k 1\ \ l \ \ r \ \ k 1  l  r  k表示对数组 a l , a l + 1 , . . . .   , a r a_l,a_{l+1},....\ ,a_r al,al+1,.... ,ar进行 + k +k +k的操作
  2. 2    l    r 2\ \ l \ \ r 2  l  r表示询问要使得原数组 h l , h l + 1 ,   . . .   , h r h_l,h_{l+1},\ ... \ ,h_r hl,hl+1, ... ,hr恰好等于 a l , a l + 1 , . . . .   , a r a_l,a_{l+1},....\ ,a_r al,al+1,.... ,ar的最小操作数

思路:
差分数组,设 d i = a i − a i − 1 d_i=a_{i}-a_{i-1} di=aiai1,可以这么思考

  • 假如 d i ≥ 0 d_i\ge 0 di0,那么位置 i i i的高度必要一层层累上去,所以加入答案
  • 假如 d i < 0 d_i<0 di<0,那么位置 i i i的高度可以从前一个传过来,所以不用加到答案里
    答案就是改变后的 a l + ∑ i = l + 1 r d i a_l+ \sum_{i=l+1}^{r} d_i al+i=l+1rdi

这样思路就有了,维护两个数组:

  1. 经过操作 1 1 1进行区间加的数组 a n a_n an
  2. 在操作 1 1 1后只有单点 ∗ 2 *2 2( d l + k , d r + 1 − k d_l+k,d_{r+1}-k dl+k,dr+1k)改变的数组 d n d_n dn

树状数组就能完成这两个操作。

用区间加、单点操作写的代码

LL a[maxn], n, m, ans, d[maxn], c[maxn];
LL sum2[maxn], sum1[maxn];
LL lowbit(LL x) { return x & (-x); }
void update_bulk(LL i, LL k) {//在i位置上加上k,区间加
	LL x = i;
	while (i <= n) {
		sum1[i] += k;
		sum2[i] += k * (x - 1);
		i += lowbit(i);
	}
}
LL getsum_bulk(LL i) {//求前缀和,区间加
	LL res = 0ll, x = i;
	while (i > 0) {
		res += x * sum1[i] - sum2[i];
		i -= lowbit(i);
	}
	return res;
}
void update(LL i, LL k) {//在i位置上加k,单点加
	while (i <= n) {
		c[i] += k;
		i += lowbit(i);
	}
}

LL getsum(LL i) {//求前缀和,单点操作
	LL res = 0ll;
	while (i > 0) {
		res += c[i];
		i -= lowbit(i);
	}
	return res;
}
void init() {
	for (int i = 0; i <= n + 1; i++) {
		sum1[i] = 0ll; sum2[i] = 0ll; c[i] = 0ll;
	}
}
int main() {
	LL T, op;
	LL l, r, k;
	scl(T);
	//T = 1;
	a[0] = 0ll;
	while (T--) {
		scl(n); scl(m);
		init();
		for (LL i = 1; i <= n; i++) {
			scl(a[i]);
			update_bulk(i, a[i]);
			if (i < n)update_bulk(i + 1, -a[i]);

			d[i] = a[i] - a[i - 1];
			if (d[i] > 0)update(i, d[i]);
			//else update(i, 0ll);
		}
		while (m--) {
			scl(op);
			if (op == 1) {
				scl(l); scl(r); scl(k);
				//区间加
				update_bulk(l, k);
				update_bulk(r + 1, -k);
				
				if (d[l] > 0)update(l, -1 * d[l]);
				d[l] += k;
				if (d[l] > 0)update(l, d[l]);
				if (r == n)continue;
				if (d[r + 1] > 0)update(r + 1, -1 * d[r + 1]);
				d[r + 1] -= k;
				if (d[r + 1] > 0)update(r + 1, d[r + 1]);
			}
			else {
				scl(l); scl(r);
				printf("%lld\n", getsum_bulk(l) - getsum_bulk(l - 1) + getsum(r) - getsum(l));//a_l+sumd_l+1~r
			}
		}
	}
	return 0;
}

只用单点操作

其实可以更加简单,因为 a l = ∑ i = 1 l d i a_l=\sum_{i=1}^{l} d_i al=i=1ldi,所以只要维护两个数组,一个保证维护的数列为正,一个维护原差分数列。

LL a[maxn];
LL d[maxn];
LL c[maxn];
LL e[maxn];
LL n, m;
//LL sum2[maxn], sum1[maxn];
LL lowbit(LL x) { return x & (-x); }
void update(LL i, LL k, LL g[]) {//在i位置上加k,单点加
	while (i <= n) {
		g[i] += k;
		i += lowbit(i);
	}
}

LL getsum(LL i, LL g[]) {//求前缀和,单点操作
	LL res = 0;
	while (i > 0) {
		res += g[i];
		i -= lowbit(i);
	}
	return res;
}
void init() {
	for (int i = 0; i <= n + 50; i++) {
		//sum1[i] = 0ll; sum2[i] = 0ll; 
		e[i] = 0; c[i] = 0;
	}
}
int main() {
	LL T, op;
	LL l, r, k;
	scl(T);
	//T = 1;
	while (T--) {
		scl(n); scl(m);
		init();
		a[0] = 0;
		d[0] = 0;
		//mem(c, 0);
		//mem(e, 0);
		for (LL i = 1; i <= n; i++) {
			scl(a[i]);
			d[i] = a[i] - a[i - 1];
			update(i, d[i], e);
			if (d[i] > 0)update(i, d[i], c);
		}
		while (m--) {
			scl(op); scl(l); scl(r);
			if (l > r)swap(r, l);
			if (op == 1) {
				scl(k);
				update(l, k, e);
				update(r + 1, -k, e);

				if (d[l] > 0)update(l, -1ll * d[l], c);
				d[l] += k;
				if (d[l] > 0)update(l, d[l], c);

				if (d[r + 1] > 0)update(r + 1, -1ll * d[r + 1], c);
				d[r + 1] -= k;
				if (d[r + 1] > 0)update(r + 1, d[r + 1], c);

			}
			else {
				printf("%lld\n", getsum(l, e) + getsum(r, c) - getsum(l, c));//a_l+sumd_l+1~r
			}
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值