KTT

我们考虑传统的区间最大子段和方法,维护最大前后缀和 ls,rs 和最大子段和 mx,这些东西直接区间加是不好维护的。但我们发现,设选择的长度是变量 x,区间加上的值为 k,这些东西都可以写成一个 kx+b 的一次函数形式,考虑维护这些一次函数。

具体的,我们在每个节点维护一个阈值 x,表示要改变上述三个函数中任意一个所需要的最小增量。对于一次区间加操作,如果加数 <=x 就正常做,否则就暴力递归下去。

考虑 update 操作。对于 ls,原始的更新式子是 max(ls,ls+sum),我们将阈值设为这两个二次函数的交点,如果交点 <=0 或不存在则是 。对于 rs,mx 同理,三个取 min 即可得到阈值。

考虑看起来非常不对的时间复杂度:对于 ls 和 rs 的更新,显然每次只加正数时,它们的长度只会增加,复杂度 O(nlog2n)

对于 mx 的更新,它有三种情况:左、右、左+右。设为 a,b,c,令当前节点选择的情况为 p,深度为 d,定义一个节点的势能为 d([kp<ka]+[kp<kb]+[kp<kc]),对于一次暴力递归操作,一个节点的 kp 一定会增大,势能减少了 d,而父亲至多只会增加 d1 的势能。我们用 O(logn) 的时间是势能减少了至少 1

考虑总势能,初始有 O(nlogn) 的势能,一次修改会改 logn 个节点,对于 mx 会增加 O(log2n) 的势能。对于前后缀和会增加 O(nlogn) 次,至多贡献 O(nlog2n) 的势能。所以总势能是 O((n+q)log2n) 的,我们用 O(logn) 的时间消耗掉一个势能,得到复杂度的一个上界是 O((n+q)log3n)。但是这个分析非常宽松,实际效率和 O(nlogn) 差不多。

#include <bits/stdc++.h>

const int N = 4e5 + 5;

using namespace std;

inline int read() {
	register int s = 0, f = 1; register char ch = getchar();
	while (!isdigit(ch)) f = (ch == '-' ? -1 : 1), ch = getchar();
	while (isdigit(ch)) s = (s * 10) + (ch & 15), ch = getchar();
	return s * f;
}

typedef long long ll;

const ll inf = 1e16;

struct func {
	int k; ll b;
	inline func() { k = b = 0; }
	inline func(int K, ll B) : k(K), b(B) { }
};

inline func operator + (func a, func b) {
	return func(a.k + b.k, a.b + b.b);
}

inline pair<func, ll> calc(func a, func b) {
	if (a.k < b.k || (a.k == b.k && a.b < b.b)) swap(a, b);
	if (a.b >= b.b) return make_pair(a, inf);
	return make_pair(b, (b.b - a.b) / (a.k - b.k));
}

struct node {
	func ls, rs, mx, sum;
	ll x;
};

inline node operator + (node a, node b) {
	node res; pair<func, ll> t;
	res.x = min(a.x, b.x);
	t = calc(a.ls, a.sum + b.ls); res.ls = t.first; res.x = min(res.x, t.second);
	t = calc(b.rs, a.rs + b.sum); res.rs = t.first; res.x = min(res.x, t.second);
	t = calc(a.mx, b.mx); res.x = min(res.x, t.second);
	t = calc(t.first, a.rs + b.ls); res.mx = t.first; res.x = min(res.x, t.second);
	res.sum = a.sum + b.sum;
	return res;
}

node p[N << 2];
ll tag[N << 2];
int n, q, a[N];

inline void update(int now) {
	p[now] = p[now << 1] + p[now << 1 | 1];
}

inline void upd(int now, ll k) {
	tag[now] += k;
	p[now].x -= k;
	p[now].ls.b += k * p[now].ls.k;
	p[now].rs.b += k * p[now].rs.k;
	p[now].mx.b += k * p[now].mx.k;
	p[now].sum.b += k * p[now].sum.k;
}

inline void pushdown(int now) {
	if (tag[now]) {
		upd(now << 1, tag[now]);
		upd(now << 1 | 1, tag[now]);
		tag[now] = 0;
	}
}

inline void build(int now, int l, int r) {
	if (l >= r) {
		p[now].ls = p[now].rs = p[now].mx = p[now].sum = func(1, a[l]);
		p[now].x = inf; return ;
	} int mid = l + r >> 1;
	build(now << 1, l, mid); build(now << 1 | 1, mid + 1, r);
	update(now);
}

inline void modify(int now, int l, int r, int ql, int qr, ll k) {
	if (ql <= l && r <= qr) {
		if (k > p[now].x) {
			int mid = l + r >> 1;
			k += tag[now]; tag[now] = 0;
			modify(now << 1, l, mid, ql, qr, k);
			modify(now << 1 | 1, mid + 1, r, ql, qr, k);
			update(now);
		} else upd(now, k);
		return ;
	} pushdown(now); int mid = l + r >> 1;
	if (ql <= mid) modify(now << 1, l, mid, ql, qr, k);
	if (qr > mid) modify(now << 1 | 1, mid + 1, r, ql, qr, k);
	update(now);
}

inline node query(int now, int l, int r, int ql, int qr) {
	if (ql <= l && r <= qr) return p[now];
	pushdown(now); int mid = l + r >> 1;
	if (qr <= mid) return query(now << 1, l, mid, ql, qr);
	if (ql > mid) return query(now << 1 | 1, mid + 1, r, ql, qr);
	return query(now << 1, l, mid, ql, qr) + query(now << 1 | 1, mid + 1, r, ql, qr);
}

int main() {
	n = read(); q = read();
	for (int i = 1; i <= n; ++i) a[i] = read();
	build(1, 1, n); int op, l, r, k;
	while (q--) {
		op = read(); l = read(); r = read();
		if (op == 1) {
			k = read();
			modify(1, 1, n, l, r, k);
		} else {
			node t = query(1, 1, n, l, r);
			printf("%lld\n", max(0ll, t.mx.b));
		}
	}
	return 0;
}
posted @   Smallbasic  阅读(125)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
点击右上角即可分享
微信分享提示