HDU6701(2019杭电多校10):Make Rounddog Happy(单调栈 + 预处理(或分治 + 预处理))

3 篇文章 0 订阅
0 篇文章 0 订阅

在这里插入图片描述
题目大意:初始有一个序列 a,定义 a 的一个子序列是好序列:若 [ l , r ] [l,r] [l,r] m a x ( a l , a l + 1 , . . , a r ) − ( r − l + 1 ) ≤ k max(a_l,a_{l + 1},..,a_r) - (r - l + 1) \leq k max(al,al+1,..,ar)(rl+1)k a l , a l + 1 , . . . , a r a_l,a_{l + 1},...,a_r al,al+1,...,ar元素不重复。让你求 a 序列中有多少好序列。

做法:先考虑无元素不重复限制的做法:处理出每个元素左边一个比他大和右边第一个比他大,由于区间长度必须大于 (max - k),对于当前每个处理元素:可以枚举左端点,计算有多少合法的右端点,然后统计答案,因为没有限制,只要长度超过 (max - k),且覆盖了当前处理元素的右端点都是合法的。

考虑有元素不能重复这个限制,前面过程一样,考虑统计答案时,枚举左端点 i,长度必须大于 x,因此右端点最靠左的位置为 i + x - 1,右端点最靠右的位置不再是当前元素作为最大值的右区间,而是以 i 为起点,向右走元素不重复的情况下能走到的最大位置(需要多一个预处理),处于这个范围内的右端点是合法的右端点。

所以具体做法是:先预处理出以每个位置为起点,向右走元素不重复的情况下的能走到的最大的位置,为了保证复杂度是 n l o g n nlogn nlogn,还可以处理出以每个位置为起点向左走能走的最小位置。这样可以在统计答案时枚举右端点计算有多少合法的左端点,选择较短的一边来枚举统计答案,从而保证复杂度不会退化。
关于统计答案,可以用分治,也可以用单调栈处理出每个值作为最大值时的左端点和右端点,然后扫一遍。复杂度都将是 n l o g n nlogn nlogn,只能优化常数

以下是单调栈代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e5 + 10;
typedef long long ll;
int t,n,k;
int L[maxn],l[maxn],R[maxn],r[maxn],nxt[maxn],lst[maxn];
int a[maxn];
int sta[maxn],top;
ll res = 0;
void init() {
	res = top = 0;
	fill(L,L + n + 1,0);
	fill(R,R + n + 1,0);
	for(int i = 0; i <= n + 1; i++) {
		l[i] = lst[i] = 0;
		r[i] = nxt[i] = n + 1;
	}	
}
ll solve(int p) {
	ll ans = 0;
	int mid = L[p] + R[p] >> 1;
	int x = a[p] - k;
	if(p <= mid) {
		for(int i = L[p] + 1; i <= p; i++) {
			int lp = max(p,i + x - 1);
			int rp = min(R[p] - 1,r[i] - 1);
			if(rp >= lp) ans += (rp - lp + 1);
		}
	}	
	else {
		for(int i = p; i < R[p]; i++) {
			int rp = min(p,i - x + 1);
			int lp = max(L[p] + 1,l[i] + 1);
			if(rp >= lp) ans += (rp - lp + 1);
		}
	}
	return ans;
}
int main() {
	scanf("%d",&t);
	while(t--) {
		scanf("%d%d",&n,&k);
		init();
		for(int i = 1; i <= n; i++) scanf("%d",&a[i]);
		for(int i = 1; i <= n; i++) {
			l[i] = max(l[i - 1],lst[a[i]]);
			lst[a[i]] = i;
			while(top > 0 && a[sta[top]] <= a[i]) {
				R[sta[top]] = i;
				top--;
			} 
			sta[++top] = i;
		}
		while(top > 0) {
			R[sta[top]] = n + 1;
			top--;
		}
		for(int i = n; i >= 1; i--) {
			r[i] = min(r[i + 1],nxt[a[i]]);
			nxt[a[i]] = i;
			while(top > 0 && a[sta[top]] <= a[i]) {
				L[sta[top]] = i;
				top--;
			}
			sta[++top] = i;
		}
		while(top > 0) {
			L[sta[top]] = 0;
			top--;
		}
		for(int i = 1; i <= n; i++)
			res += solve(i);
		printf("%lld\n",res);
	}
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值