CF407 E. k-d-sequence(线段树+单调栈)

CF407 E. k-d-sequence

problem

solution

  • special case d = 0 d=0 d=0,相当于寻找最长的一段数字相同的区间

  • other case,如果要满足公差为 d d d等差序列

    • 区间内每个数在模 d d d意义下同余
    • 每个数互不相同
  • 算法流程

    • 先将序列分成若干个同余 m m m的子区间,从左往右扫一遍,即可得到

    • 对于同余的子区间,把所有数进行 x − r d \frac{x-r}{d} dxr的操作,转化为求公差为 1 1 1的等差数列

    • 对于区间 [ l , r ] [l,r] [l,r],需要增加的个数 max ⁡ { x i ∣ l ≤ i ≤ r } − min ⁡ { x i ∣ l ≤ i ≤ r } + 1 − ( r − l + 1 ) \max\{x_i|l\le i\le r\}-\min\{x_i|l\le i\le r\}+1-(r-l+1) max{xilir}min{xilir}+1(rl+1)

      满足增加个数 ≤ k \le k k

    • 从小到大顺次枚举 r r r,那么就是要最小化 l l l

      • [ l , r ] [l,r] [l,r]区间不重复

        可以通过map快速查到与 x r x_r xr值相同的点的位置,假设为 p o s pos pos

        则需满足 p o s < l pos<l pos<l

      • 加的数个数不能超过 k k k

        max ⁡ { x i ∣ l ≤ i ≤ r } − min ⁡ { x i ∣ l ≤ i ≤ r } + 1 − ( r − l + 1 ) ≤ k \max\{x_i|l\le i\le r\}-\min\{x_i|l\le i\le r\}+1-(r-l+1)\le k max{xilir}min{xilir}+1(rl+1)k

        ⇕ \Updownarrow

        max ⁡ { x i ∣ l ≤ i ≤ r } − min ⁡ { x i ∣ l ≤ i ≤ r } + l ≤ k + r \max\{x_i|l\le i\le r\}-\min\{x_i|l\le i\le r\}+l\le k+r max{xilir}min{xilir}+lk+r

        用线段数维护 w l = max ⁡ { x i ∣ l ≤ i ≤ r } − min ⁡ { x i ∣ l ≤ i ≤ r } + l w_l=\max\{x_i|l\le i\le r\}-\min\{x_i|l\le i\le r\}+l wl=max{xilir}min{xilir}+l

        l l l的下界为 p o s pos pos,则要在 [ p o s , r ] [pos,r] [pos,r]找最左边的 l l l,满足 w l ≤ k + r w_l\le k+r wlk+r

  • 最后只剩下如何维护 w w w

    单调栈,维护一个递增单调栈和一个递减单调栈。以递减为例

    递减单调栈当一个大于栈顶的元素加入时,会不断弹出栈顶,因此单调栈可以将 m a x ( L , R ) max(L,R) max(L,R)分成递减的若干段

    单调栈中的一个点其实代表的是一个区间,弹栈顶相当于最大值变化

    被弹出的元素的线段树的最大值变化即是线段树上区间加
    在这里插入图片描述

code

#include <map>
#include <cstdio>
#include <iostream>
using namespace std;
#define int long long
#define maxn 200005
map < int, int > last;
int n, k, d, pos, flag, ans_l = 1, ans_r = 1;
int a[maxn], Min[maxn], Max[maxn];
int t[maxn << 4], tag[maxn << 4];

void build( int num, int l, int r ) {
	t[num] = l, tag[num] = 0;
	if( l == r ) return;
	int mid = ( l + r ) >> 1;
	build( num << 1, l, mid );
	build( num << 1 | 1, mid + 1, r );
}

void pushdown( int num ) {
	t[num << 1] += tag[num];
	tag[num << 1] += tag[num];
	t[num << 1 | 1] += tag[num];
	tag[num << 1 | 1] += tag[num];
	tag[num] = 0;
}

void modify( int num, int l, int r, int pos ) {
	pushdown( num );
	if( l == r ) {
		t[num] = 0;
		return;
	}
	int mid = ( l + r ) >> 1;
	if( pos <= mid ) modify( num << 1, l, mid, pos );
	else modify( num << 1 | 1, mid + 1, r, pos );
	t[num] = min( t[num << 1], t[num << 1 | 1] );
}

void modify( int num, int l, int r, int L, int R, int w ) {
	if( R < l || r < L ) return; 
	if( L <= l && r <= R ) {
		t[num] += w, tag[num] += w;
		return;
	}
	pushdown( num );
	int mid = ( l + r ) >> 1;
	modify( num << 1, l, mid, L, R, w );
	modify( num << 1 | 1, mid + 1, r, L, R, w );
	t[num] = min( t[num << 1], t[num << 1 | 1] );
}

void find( int num, int l, int r, int k ) {
	if( l == r ) {
		pos = l, flag = 1;
		return;
	}
	pushdown( num );
	int mid = ( l + r ) >> 1;
	if( t[num << 1] <= k ) find( num << 1, l, mid, k );
	else find( num << 1 | 1, mid + 1, r, k );
}

void query( int num, int l, int r, int L, int R, int k ) {
	if( flag || r < L || R < l ) return;
	if( L <= l && r <= R ) {
		if( t[num] <= k ) find( num, l, r, k );
		return;
	}
	pushdown( num );
	int mid = ( l + r ) >> 1;
	query( num << 1, l, mid, L, R, k );
	query( num << 1 | 1, mid + 1, r, L, R, k );
}

signed main() {
	scanf( "%lld %lld %lld", &n, &k, &d );	
	for( int i = 1;i <= n;i ++ )
		scanf( "%lld", &a[i] );
	if( ! d ) {
		int l = 0, r = 0;
		for( int i = 1;i <= n;i ++ ) {
			if( a[i] != a[i - 1] ) l = r = i;
			else ++ r;
			if( r - l > ans_r - ans_l ) ans_r = r, ans_l = l;
		}
		return ! printf( "%lld %lld\n", ans_l, ans_r );
	}
	build( 1, 1, n );
	int min_top = 0, max_top = 0;
	for( int r = 1, l = 1;r <= n;r ++ ) {
		int t = l;
		if( ( a[r] - a[r - 1] ) % d ) l = r;//要求同余
		else l = max( l, last[a[r]] + 1 );//取max维护不重复的条件
		last[a[r]] = r;
		while( t < l ) modify( 1, 1, n, t ++ );//清除不属于[l,r]区间的所有线段树痕迹
		//Min:维护min{a[i]|l<=i<=r}的递增栈
		//Max:维护max{a[i]|l<=i<=r}的递减栈
        //栈内点管辖一个区间eg:s[top]管辖(s[top-1],s[top]] 
        //w[l]=max[L,R]-min[L,R]+l
		while( min_top && Min[min_top] >= l && a[Min[min_top]] > a[r] ) {
			modify( 1, 1, n, Min[min_top - 1] + 1, Min[min_top], a[Min[min_top]] / d );//[L,R]中最小值变小 先把之前-min(L,R)的贡献抵消掉 所以是+
			min_top --;
		}
		//把现在真正的min(L,R)贡献放进去 所以是- 
		modify( 1, 1, n, max( l, Min[min_top] + 1 ), r, -a[r] / d );
		Min[++ min_top] = r;
		while( max_top && Max[max_top] >= l && a[Max[max_top]] < a[r] ) {
			modify( 1, 1, n, Max[max_top - 1] + 1, Max[max_top], -a[Max[max_top]] / d );
			max_top --;
		}
        //取左端点max比较是保证单调栈中每个点管辖区间不重复且并集为整个大区间 
		modify( 1, 1, n, max( l, Max[max_top] + 1 ), r, a[r] / d );
		Max[++ max_top] = r;
		flag = 0, pos = 0;
		query( 1, 1, n, l, r, k + r );
		if( r - pos > ans_r - ans_l ) ans_l = pos, ans_r = r;
	}
	printf( "%lld %lld\n", ans_l, ans_r );
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值