P5609 [Ynoi2013] 对数据结构的爱 题解

事实上一个区间的答案就是区间和减去 pp 乘以减去的次数。

于是问题难点在于如何求一个区间减去的次数。

注意到 n106n \leq 10^6,猜测这可能不是根号科技。考虑朴素线段树。容易观察到的一点是,对于每个区间,假设进去时的初始值为 xx。然后依次加上区间每个数并取模的时候,xx 上升的同时,减去 pp 的次数应该是单调不降的。此外我们还发现对于区间 [l,r][l,r],减去的次数 [0,rl+1]\in [0,r-l+1]

我们可以对于线段树每个区间,维护 cxc_x 表示想让区间减去次数 x\geq x,至少进入多少。首先必然有 cc 单调不降。

如果我们知道了所有区间的每个 cc,考虑如何求答案?这是很容易的。对于线段树每个极大区间,从左往右依次进入并二分,把得到的值作为下个区间开始值不断维护,复杂度单次 O(log2n)O(\log^2 n)

问题是怎么求每个区间的 cc。一个非常直接的想法是考虑左儿子的区间减 xx 次,右儿子区间减 yy 次。然而前提是 sumlson+clson,x+11x×pcrson,ysum_{lson}+c_{lson,x+1}-1-x\times p \geq c_{rson,y}。即左儿子区间减 xx 次能达到最大结果在右儿子区间能减 yy 次。如果满足这个限制,那么 (x,y)(x,y)cx+yc_{x+y} 的贡献为 max{clson,x,crson,ysumlson+x×p}\max\{c_{lson,x},c_{rson,y}-sum_{lson}+x\times p\}。即这个数要在左边能减 xx 次,且在左边结果要在右边能减 yy 次。

直接合并复杂度是两个儿子区间长度乘积,显然不太对,考虑观察性质。

性质 11 是,总有 cx+1cxpc_{x+1}-c_x \geq p 成立。因为我想让后面能减少多一次 pp,那之前就应该多加一个 pp

有这个性质能干什么?考虑从小到大枚举上问的 xx。考虑那个限制是 sumlson+clson,x+11x×pcrson,ysum_{lson}+c_{lson,x+1}-1-x\times p \geq c_{rson,y}。考虑 xx+1x \leftarrow x+1,左边容易发现是不降的。所以对于每个 xx,符合条件的 yy 是一段前缀。于是可以双指针求出每个 xx 对应所有 yy

但是尽管这样,对于每个 xx,还是要枚举前缀更新,复杂度还是不对。

考虑对答案的贡献这个式子:max{clson,x,crson,ysumlson+x×p}\max\{c_{lson,x},c_{rson,y}-sum_{lson}+x\times p\}。事实上,可以得到 (x,y)(x,y) 的贡献一定 (x+1,y1)\leq (x+1,y-1) 的贡献。具体地可以通过 x,yx,y 的限制来说明。于是每次 xx+1x\leftarrow x+1 只需要更新那些新的 yy 即可。

于是我们做到了 O(nlogn+mlog2n)O(n \log n+m \log^2 n) 的复杂度。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <queue>
#include <vector>
using namespace std;

#define int long long

const int N = 1e6 + 5;

int n, m, p, a[N];

class SegmentTree
{
public:
	struct Node
	{
		int l, r;
		vector<long long> v;
		long long sum;
	}tr[N << 2];
	void pushup(int u)
	{
		tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
		int l = tr[u].l, r = tr[u].r, mid = l + r >> 1;
		int y = 0;
		for (int x = 0; x <= mid - l + 1; x++)
		{
			if (y == r - mid + 1) y--;
			if (y < 0) y = 0;
			for (; y <= r - mid; y++)
			{
				long long g = tr[u << 1].v[x + 1] - 1 + tr[u << 1].sum - p * x;
				long long k = tr[u << 1 | 1].v[y];
				if (g < k)
				{
					y--;
					break;
				}
				tr[u].v[x + y] = min(tr[u].v[x + y], max(tr[u << 1].v[x], tr[u << 1 | 1].v[y] - tr[u << 1].sum + x * p));
			}
		}
	}
	void build(int u, int l, int r)
	{
		tr[u] = { l, r };
		tr[u].sum = a[r];
		tr[u].v.resize(r - l + 3);
		for (int i = 0; i < r - l + 3; i++) tr[u].v[i] = (long long)4e18;
		tr[u].v[0] = (long long)-4e18;
		if (l == r)
		{
			tr[u].v[1] = p - a[r];
			return;
		}
		int mid = l + r >> 1;
		build(u << 1, l, mid);
		build(u << 1 | 1, mid + 1, r);
		pushup(u);
	}
	long long query(int u, int l, int r, long long x)
	{
		if (tr[u].l >= l and tr[u].r <= r)
		{
			auto it = upper_bound(tr[u].v.begin() + 1, tr[u].v.end(), x);
			--it;
			long long g = tr[u].sum + x - (it - tr[u].v.begin()) * p;
			return g;
		}
		int mid = tr[u].l + tr[u].r >> 1;
		if (r <= mid) return query(u << 1, l, r, x);
		if (l > mid) return query(u << 1 | 1, l, r, x);
		return query(u << 1 | 1, l, r, query(u << 1, l, r, x));
	}
}sgt;

signed main()
{
	ios::sync_with_stdio(0), cin.tie(0);
	cin >> n >> m >> p;
	for (int i = 1; i <= n; i++) cin >> a[i];
	sgt.build(1, 1, n);
	while (m--)
	{
		int l, r;
		cin >> l >> r;
		cout << sgt.query(1, l, r, 0ll) << "\n";
	}
	return 0;
}
posted @   HappyBobb  阅读(6)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示