HNOI2016序列+数据加强版(前缀和+单调栈)

普通版:题目链接
数据加强版:题目链接
数据加强版 加强版:题目链接

题目大意

给定一个数列,每次给出一个区间,求区间中所有子段的最小值之和。
n , m ≤ 1 0 5 n,m\le 10^5 n,m105

题解

显然是单调栈先跑一遍,然后接下来有若干个做法:

1.莫队

这题莫队解法也很神啊qwq
考虑加入点时对答案的贡献,以右端点为例。令 p p p表示当前询问区间 [ l , r ] [l,r] [l,r]中的最小值,那么 r r r的贡献实际上就是 a p ( p − l + 1 ) + s [ r ] − s [ p ] a_p(p-l+1)+s[r]-s[p] ap(pl+1)+s[r]s[p],其中 s [ r ] s[r] s[r]表示区间 [ 1 , r ] [1,r] [1,r] r r r为右端点的贡献,这个在单调栈的时候就可以dp出来。
于是用st表查询最小值就可以在 O ( n n ) O(n\sqrt n) O(nn )的时间内解决了。

2.前缀和

我们考虑上面的莫队算法,如果询问区间为 [ l , r ] [l,r] [l,r],令最小值为 p p p,那么考虑如果从 p p p开始不断往左右两边加点,得到的答案为
∑ i = p + 1 r s [ i ] − s [ p ] ( r − p ) + ∑ i = l p − 1 s ′ [ i ] − s ′ [ p ] ( p − l ) + a [ p ] ( r − p + 1 ) ( p − l + 1 ) \sum_{i=p+1}^rs[i]-s[p](r-p)+\sum_{i=l}^{p-1}s'[i]-s'[p](p-l)+a[p](r-p+1)(p-l+1) i=p+1rs[i]s[p](rp)+i=lp1s[i]s[p](pl)+a[p](rp+1)(pl+1)
显然我们再记一个 s s s的前缀和就可以 O ( 1 ) O(1) O(1)回答询问了。因此总复杂度为 O ( n l o g n + m ) O(nlogn+m) O(nlogn+m),可以做完数据加强版。

3.优化最值查询

但是数据加强加强版过不了,因为瓶颈卡在了 O ( n l o g n ) O(nlogn) O(nlogn)的空间复杂度上。接下来就有了一个很神仙的ST表+分块做法。我们把原数列按照大小为 B B B分块, p r e [ i ] , s u f [ i ] pre[i],suf[i] pre[i],suf[i]分别表示从 i i i到块开头/结尾的最小值位置。
再考虑我们对于这些块建立ST表,那么如果左右端点在一个块内,我们暴力;否则我们 O ( 1 ) O(1) O(1)就可以利用上面那些东西询问最值。这个做法空间复杂度 O ( n B l o g n ) O(\frac nBlogn) O(Bnlogn),时间复杂度 O ( m B ) O(mB) O(mB),因此 B B B大概开个10左右就行了。
下面附上加强版加强版的代码。

#include <bits/stdc++.h>
namespace IOStream {
	const int MAXR = 10000000;
	char _READ_[MAXR], _PRINT_[MAXR];
	int _READ_POS_, _PRINT_POS_, _READ_LEN_;
	inline char readc() {
	#ifndef ONLINE_JUDGE
		return getchar();
	#endif
		if (!_READ_POS_) _READ_LEN_ = fread(_READ_, 1, MAXR, stdin);
		char c = _READ_[_READ_POS_++];
		if (_READ_POS_ == MAXR) _READ_POS_ = 0;
		if (_READ_POS_ > _READ_LEN_) return 0;
		return c;
	}
	template<typename T> inline void read(T &x) {
		x = 0; register int flag = 1, c;
		while (((c = readc()) < '0' || c > '9') && c != '-');
		if (c == '-') flag = -1; else x = c - '0';
		while ((c = readc()) >= '0' && c <= '9') x = x * 10 - '0' + c;
		x *= flag;
	}
	template<typename T1, typename ...T2> inline void read(T1 &a, T2&... x) {
		read(a), read(x...);
	}
	inline int reads(char *s) {
		register int len = 0, c;
		while (isspace(c = readc()) || !c);
		s[len++] = c;
		while (!isspace(c = readc()) && c) s[len++] = c;
		s[len] = 0;
		return len;
	}
	inline void ioflush() { fwrite(_PRINT_, 1, _PRINT_POS_, stdout), _PRINT_POS_ = 0; fflush(stdout); }
	inline void printc(char c) {
		_PRINT_[_PRINT_POS_++] = c;
		if (_PRINT_POS_ == MAXR) ioflush();
	}
	inline void prints(char *s) {
		for (int i = 0; s[i]; i++) printc(s[i]);
	}
	template<typename T> inline void print(T x, char c = '\n') {
		if (x < 0) printc('-'), x = -x;
		if (x) {
			static char sta[20];
			register int tp = 0;
			for (; x; x /= 10) sta[tp++] = x % 10 + '0';
			while (tp > 0) printc(sta[--tp]);
		} else printc('0');
		printc(c);
	}
	template<typename T1, typename ...T2> inline void print(T1 x, T2... y) {
		print(x, ' '), print(y...);
	}
}
using namespace IOStream;
using namespace std;
typedef long long ll;

int A, B, C, P;
ll lastans;
inline int rnd() { return A = (A * B + (C ^ (int)(lastans & 0x7FFFFFFF)) % P) % P; }
const int MAXN = 3000005, M = 8, MOD = 1000000007;
int st[20][MAXN / M + 5], sta[MAXN], n, m;
int val[MAXN], lg[MAXN], tta[MAXN];
ll pre1[MAXN], pre2[MAXN], suf1[MAXN], suf2[MAXN];
inline int get_min(int x, int y) { return val[x] < val[y] ? x : y; }
inline int query_min(int l, int r) {
	int tl = (l - 1) / M + 1, tr = (r - 1) / M + 1;
	if (tl == tr) {
		int mn = l;
		for (int i = l + 1; i <= r; i++) if (val[i] < val[mn]) mn = i;
		return mn;
	}
	if ((++tl) > (--tr)) return get_min(tta[l], sta[r]);
	int x = lg[tr - tl + 1];
	return get_min(get_min(st[x][tl], st[x][tr - (1 << x) + 1]), get_min(tta[l], sta[r]));
}
int main() {
	read(n, m);
	for (int i = 1; i <= n; i++) read(val[i]);
	read(A, B, C, P);
	int tp = 0;
	for (int i = 1; i <= n; i++) {
		for (; tp > 0 && val[sta[tp]] >= val[i]; --tp) lg[sta[tp]] = i;
		pre1[i] = pre1[sta[tp]] + (ll)val[i] * (i - sta[tp]);
		pre2[i] = pre2[i - 1] + pre1[i];
		sta[++tp] = i;
	}
	while (tp > 0) lg[sta[tp--]] = n + 1;
	for (int i = n; i > 0; i--) {
		suf1[i] = suf1[lg[i]] + (ll)val[i] * (lg[i] - i);
		suf2[i] = suf2[i + 1] + suf1[i];
	}
	int mm = (n + M - 1) / M;
	for (int i = 1; i <= n; i++) {
		if ((i - 1) % M) sta[i] = val[i] > val[sta[i - 1]] ? sta[i - 1] : i;
		else sta[i] = i;
	}
	for (int i = n; i > 0; i--) {
		if (i % M) tta[i] = val[i] > val[tta[i + 1]] ? tta[i + 1] : i;
		else tta[i] = i;
	}
	for (int i = 1; i <= mm; i++) st[0][i] = sta[min(i * M, n)];
	for (int i = 1; i < 20; i++)
	for (int j = 1; j + (1 << i) - 1 <= mm; j++)
		st[i][j] = get_min(st[i - 1][j], st[i - 1][j + (1 << i >> 1)]);
	lg[1] = 0;
	for (int i = 2; i <= mm; i++) lg[i] = lg[i >> 1] + 1;
	ll res = 0;
	while (m--) {
		int l = rnd() % n + 1, r = rnd() % n + 1;
		if (l > r) swap(l, r);
		int t = query_min(l, r);
		res += (lastans = (ll)(r - t + 1) * (t - l + 1) * val[t] + pre2[r] - pre2[t] - pre1[t] * (r - t) + suf2[l] - suf2[t] - suf1[t] * (t - l)) % MOD;
	}
	printf("%lld\n", (res % MOD + MOD) % MOD);
	return 0;
}
要使用并查集来维护前缀和,你可以按照以下步骤进行操作: 1. 首先,需要定义一个并查集的数据结构,包括父节点数组fa和值数组val。其中,fa[i]代表节点i的父节点,val[i]代表节点i与其父节点的差值。 2. 初始化并查集,将每个节点的父节点设置为自身,差值设置为0。 3. 遍历每一个需要进行操作的元素。对于每一个元素,将其与其父节点的差值与当前节点的值相加,得到新的节点值。 4. 如果当前节点与父节点的差值与新的节点值不相等,则说明存在不满足前缀和关系的情况,将变量o设置为false。 5. 如果当前节点与父节点的差值与新的节点值相等,则更新当前节点的父节点为新的父节点,更新当前节点的差值为新的差值。 6. 最后,根据变量o的值判断是否存在不满足前缀和关系的情况。如果o为true,则输出"true",否则输出"false"。 以下是使用并查集维护前缀和的示例代码: ```cpp #include <cstdio> #include <algorithm> #include <cstring> using namespace std; int fa += val = k; } int main() { int T; scanf("%d", &T); int s, t, v; while(T--) { scanf("%d%d", &n, &m); for (int i = 0; i <= n + 1; i++) fa[i = i; memset(val, 0, sizeof(val)); bool o = true; for (int i = 1; i <= m; i++) { scanf("%d%d%d", &s, &t, &v); int fs = find(s - 1), ft = find(t); if (fs == ft) { if (val[s - 1 - val[t != v) o = false; } if (fs != ft) { fa = ft; val = val[t - val[s - 1 + v; } } if (o) printf("true\n"); else printf("false\n"); } return 0; } ``` 在上述代码中,使用并查集来维护前缀和的过程是在find函数中进行的。在查找节点的同时,更新节点的值,实现了维护前缀和的功能。在遍历每个元素之后,根据o的值输出最终的结果。 请注意,上述代码是一个示例,具体的实现可能因问题的要求和代码的细节而有所不同。因此,在实际应用中,你可能需要根据具体情况进行相应的调整。<span class="em">1</span><span class="em">2</span> #### 引用[.reference_title] - *1* [java8集合源码分析-Happy-With-Data-Structure:学习数据结构的一些代码,包括数组,链表,二分搜索...并查集,](https://download.csdn.net/download/weixin_38661100/19390436)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [[HNOI2005]狡猾的商人(并查集维护前缀和)](https://blog.csdn.net/zhhx2001/article/details/51810163)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值