D. Petya and Array(树状数组、动态开点)

D. Petya and Array

传送门

题目描述

在这里插入图片描述

输入输出

在这里插入图片描述

样例

样例输入#1

5 4
5 -1 3 4 -1

样例输出#1

5

样例输入#2

3 0
-1 2 -3

样例输出#2

4

样例输入#3

4 -1
-2 1 -2 3

样例输出#3

3


数据范围

1 < = n < = 2 ∗ 1 0 5 , ∣ a i ∣ < = 1 0 9 , ∣ t ∣ < = 2 ∗ 1 0 14 {1<=n<=2*10^5,|a_i| <=10^9,|t|<=2*10^{14}} 1<=n<=2105ai<=109t<=21014


思路

s u m ( r ) − s u m ( l − 1 ) < t {{sum(r) -sum(l-1)<t}} sum(r)sum(l1)<t,其中 s u m {sum} sum 是前缀和,其中元素有正有负。

问这样的数对 ( l , r ) {(l,r)} l,r 有多少个?

树状数组

  1. 枚举 1 − n {1-n} 1n 的每个数作为结尾,找到每个数前面前缀大于 s [ i ] − t {s[i] - t} s[i]t 的前缀有多少。
  2. 直接开一个前缀数组 s {s} s, 从小到大排序。
  3. 从前遍历,先将 s [ i − 1 ] ( 即 上 一 轮 的 p r e ) {s[i - 1](即上一轮的pre)} s[i1]pre,在排序的前缀和数组 s {s} s 中,二分出大于 p r e {pre} pre 的下标 l {l} l,然后插入到树状数组中。
  4. 当前枚举的前缀和 p r e ( p r e + = a [ i ] ; ) {pre(pre+=a[i];)} prepre+=a[i];,在排序的前缀和数组 s {s} s 中,二分出大于 p r e − t {pre-t} pret 的下标 r {r} r
  5. 大于这个 r {r} r 的即为答案做贡献。
  6. 这里需要注意,0 号位也参与。( 1 < = l < = n , 0 < = l − 1 < = n − 1 {1<=l<=n,0<=l-1<=n-1} 1<=l<=n,0<=l1<=n1
  7. 树状数组维护的是 i {i} i 前面有几个数小于等于它。

权值线段树

  • s [ r ] − s [ l − 1 ] < t {s[r] - s[l - 1] < t} s[r]s[l1]<t
  • s [ l − 1 ] > = s [ r ] − t + 1 {s[l-1]>=s[r] - t + 1} s[l1]>=s[r]t+1
  • s [ r ] < = s [ l − 1 ] + t − 1 {s[r]<=s[l-1] + t - 1} s[r]<=s[l1]+t1
  1. 与树状数组思路一致。
  2. 权值线段树维护值域,用于查询值落于区间 [ v a l L , v a l R ] {[valL,valR]} [valL,valR] 的个数。
  3. 由于前缀和,所以最小 − 2 e 14 {-2e14} 2e14, 由于 − t {-t} t 所以值最小会在 − 4 e 14 {-4e14} 4e14。因为权值线段树维护的都是正值,所以将所有数加上 4 e 14 {4e14} 4e14,拉到正的范畴。
  • 正向遍历

    • s [ l − 1 ] > = s [ r ] − t + 1 {s[l-1]>=s[r] - t + 1} s[l1]>=s[r]t+1
    • s [ i − 1 ] {s[i-1]} s[i1]插入树中, r {r} r即视为 i {i} i,(前面小于 i {i} i的位置都已插入树中),相当于询问前面 > = s [ i ] − t + 1 {>=s[i]-t+1} >=s[i]t+1 的值的个数
for (int i = 1; i <= n; i++) {
		modify(root, L, R, s[i - 1], 1);
		res += query(root, L, R, s[i] - t + 1, R);
	}
  • 反向遍历

    • s [ r ] < = s [ l − 1 ] + t − 1 {s[r]<=s[l-1] + t - 1} s[r]<=s[l1]+t1
    • s [ i ] {s[i]} s[i]插入树中, l {l} l即视为 i {i} i,(后面大于 i {i} i的位置都已插入树中),相当于询问后面 < = s [ i − 1 ] + t − 1 {<=s[i-1]+t-1} <=s[i1]+t1
for (int i = n; i >= 1; i--) {
		modify(root, L, R, s[i], 1);
		res += query(root, L, R, 1, s[i - 1] + t - 1);
	}

代码

  • 树状数组

int n, t;
int tr[N], s[N], a[N];

int sum(int x) {
	int res = 0;
	for (; x; x -= x & -x) res += tr[x];
	return res;
}

void add(int x, int c) {
	for (; x <= n + 1; x += x & -x) tr[x] += c;
}

void solve() {
	cin >> n >> t;
	for (int i = 1; i <= n; i++) 
		cin >> a[i], s[i] = s[i - 1] + a[i];
	
	sort(s, s + 1 + n);
	int res = 0, pre = 0;
	for (int i = 1; i <= n; i++) {
		int l = upper_bound(s, s + 1 + n, pre) - s;
		add(l, 1);
		pre += a[i];			
		int r = upper_bound(s, s + 1 + n, pre - t) - s;
		res += sum(n + 1) - sum(r);
	}
	cout << res << endl;
}
  • 权值线段树
int n, t;

struct node {
	int l, r;
	int v;
} tr[N << 5];

int s[N], root, idx;

const int L = 1;
const int R = 1e15;

void pushup(int p) {
	tr[p].v = tr[tr[p].l].v + tr[tr[p].r].v;
}

void modify(int &p, int l, int r, int x, int v) {
	if(!p) p = ++idx;
	if(l == r) { tr[p].v += v; return ;}
	int mid = l + r >> 1;
	if(x <= mid) modify(tr[p].l, l, mid, x, v);
	if(x > mid) modify(tr[p].r, mid + 1, r, x, v);
	pushup(p);
}

int query(int p, int l, int r, int ql, int qr) {
	if(!p) return 0;
	if(l >= ql && r <= qr) return tr[p].v;
	int mid = l + r >> 1;
	int v = 0;
	if(ql <= mid) v = query(tr[p].l, l, mid, ql, qr);
	if(qr > mid) v += query(tr[p].r, mid + 1, r, ql, qr);
	return v;
}

void solve() {
	cin >> n >> t;
	for (int i = 1; i <= n; i++) {
		int x; cin >> x;
		s[i] = s[i - 1] + x;
	}
	for (int i = 0; i <= n; i++) s[i] += 4e14;
	int res = 0;

// s[r] - s[l - 1] < t

// s[l - 1] >= s[r] - t + 1
	// for (int i = 1; i <= n; i++) {
		// modify(root, L, R, s[i - 1], 1);
		// res += query(root, L, R, s[i] - t + 1, R);
	// }

// s[r] <= s[l - 1] + t - 1
	for (int i = n; i >= 1; i--) {
		modify(root, L, R, s[i], 1);
		res += query(root, L, R, 1, s[i - 1] + t - 1);
	}
	cout << res << endl;
}
  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
解法:贪心算法 如果两个字符串不相等,那么它们必定至少有一位不一样。考虑对于这一位,我们应该对字符串 a 进行哪种操作,才能使得它更接近于字符串 b。 首先,我们可以通过交换字符串 a 中的两个数字,使得这一位变为我们想要的数字。如果我们把这一位变成了 b 中的数字,那么显然这一位就不需要再进行修改了。因此,我们只需要考虑把这一位变成 a 中的数字 4 或 7。 如果我们把这一位变成 a 中的数字,则需要执行一次操作;如果我们把这一位变成 a 中的数字,则需要执行一次操作。那么,我们应该采取哪种操作呢? 我们可以贪心地想,如果我们把这一位变成 a 中的数字,那么这一位和 b 中的数字就越相似,那么接下来的操作就越容易执行。因此,我们应该选择将这一位变成 a 中的数字,从而尽可能地增加和 b 相同的数字的数量。 实现时,我们可以从左到右扫描字符串 a 和 b,统计它们不同的位置的数量。对于每个不同的位置,我们都可以选择将这一位变成 4 或 7,然后更新 a 中数字 4 和 7 的数量。最终,我们就可以得到将字符串 a 转换为字符串 b 所需的最少操作数。 时间复杂度 字符串 a 和 b 的长度为 n,我们需要扫描一遍字符串并统计数字 4 和 7 的数量,因此时间复杂度为 O(n)。 空间复杂度 我们只需要存储数字 4 和 7 的数量,因此空间复杂度为 O(1)。 Python 代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ღCauchyོꦿ࿐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值