[JZOJ4665] 【GDOI2017模拟7.21】数列

题目

题目大意

给你一个数列,让你找到一个最长的连续子序列,满足在添加了至多 K K K个数之后,能够变成一条公差为 D D D的等差数列。


思考历程

一眼看上去似乎是一道神题……
没有怎么花时间思考,毕竟时间都砸到T1和T2上了。


正解

仔细推一下就会发现这种等差数列 [ j , i ] [j,i] [j,i]有三个简单的条件:

  1. 所有数模 d d d的余数相同
  2. 没有重复的数( d = 0 d=0 d=0的时候除外)
  3. m a x ( j , i ) − m i n ( j , i ) d + 1 ≤ i − j + 1 + k \frac{max(j,i)-min(j,i)}{d}+1\leq i-j+1+k dmax(j,i)min(j,i)+1ij+1+k

前面两个都能很快地预处理。
现在我们枚举一个右边界 r r r,前面两个条件可以使我们得知最远的左边界 L L L
我们把第三个条件的式子乱搞一下: m a x ( l , r ) − m i n ( l , r ) − ( r − l ) d ≤ k d max(l,r)-min(l,r)-(r-l)d\leq kd max(l,r)min(l,r)(rl)dkd
考虑一下能不能维护左边的式子。最重要的是维护最小值和最大值。
随便想想就可以知道,左边界 l l l在区间 [ L , r ] [L,r] [L,r]中,最小值的取值是一段一段的(最大值也一样)。
或许能够用这条性质来维护。
接着就可以想到单调队列。对于最小值和最大值分别维护一条单调队列。
设单调队列上某个位置的值是 x x x,上一个位置是 y y y,那么区间 [ y + 1 , x ] [y+1,x] [y+1,x]中的值为 a [ x ] a[x] a[x]
于是我们可以在线段树上区间修改。
在维护单调队列的过程中,最小(大)值会有些变化。这也可以直接在线段树上改。
至于 − ( r − l ) d -(r-l)d (rl)d,这个是很容易维护的,每次直接将区间 [ 1 , r − 1 ] [1,r-1] [1,r1] d d d就可以了。
询问的时候就是寻找线段树上最左边的小于 k d kd kd的值。这个可以维护最小值,在线段树上二分就可以维护了。


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#define N 200010
#define INF 1000000000000000000
int n;
long long K,D;
int a[N];
bool bz[N];
map<int,int> last;
int fr[N];
int q1[N],q2[N];//min max
int h1,t1,h2,t2;
long long t[N*4],lazy[N*4];
inline void pushdown(int k){
	t[k<<1]+=lazy[k],t[k<<1|1]+=lazy[k];
	lazy[k<<1]+=lazy[k],lazy[k<<1|1]+=lazy[k];
	lazy[k]=0;
}
void build(int k,int l,int r){
	t[k]=INF;
	if (l==r)
		return;
	int mid=l+r>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
}
void add(int k,int l,int r,int st,int en,long long c){
	if (st<=l && r<=en){
		t[k]+=c;
		lazy[k]+=c;
		return;
	}
	pushdown(k);
	int mid=l+r>>1;
	if (st<=mid)
		add(k<<1,l,mid,st,en,c);
	if (mid<en)
		add(k<<1|1,mid+1,r,st,en,c);
	t[k]=min(t[k<<1],t[k<<1|1]);
}
void change0(int k,int l,int r,int x){
	if (l==r){
		t[k]=0;
		return;
	}
	pushdown(k);
	int mid=l+r>>1;
	if (x<=mid)
		change0(k<<1,l,mid,x);
	else
		change0(k<<1|1,mid+1,r,x);
	t[k]=min(t[k<<1],t[k<<1|1]);
}
int query(int k,int l,int r,int en){
	if (t[k]>K*D)
		return 0;
	if (l==r)
		return l;
	pushdown(k);
	int mid=l+r>>1,res=0;
	res=query(k<<1,l,mid,en);
	if (!res && mid<en)
		res=query(k<<1|1,mid+1,r,en);
	return res;
}
int main(){
	scanf("%d%lld%lld",&n,&K,&D);
	for (int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	if (D==0){
		int ansl=1,len=1;
		for (int i=1,j=0;i<=n;++i)
			if (a[i]!=a[i-1]){
				if (i-j>len)
					len=i-j,ansl=j;
				else if (i-j==len)
					ansl=j;
			}
		printf("%d %d\n",ansl,ansl+len-1);
		return 0;
	}
	for (int i=1;i<=n;++i)
		if (i==1 || (a[i]%D+D)%D!=(a[i-1]%D+D)%D)
			bz[i]=1;
	for (int i=1;i<=n;++i){
		fr[i]=last[a[i]];
		last[a[i]]=i;	
	}
	build(1,1,n);
	h1=h2=1,t1=t2=0;
	int ansl=1,len=1;
	for (int i=1,st=1;i<=n;++i){
		if (bz[i]){
			h1=h2=1,t1=t2=0;
			if (st<=i-1)
				add(1,1,n,st,i-1,INF);
			st=i;
		}
		else if (st<=fr[i]){
			add(1,1,n,st,fr[i],INF);
			st=fr[i]+1;
		}
		while (h1<=t1 && q1[h1]<st)
			h1++;
		while (h2<=t2 && q2[h2]<st)
			h2++;
		while (h1<=t1 && a[q1[t1]]>=a[i]){
			add(1,1,n,q1[t1-1]+1,q1[t1],+a[q1[t1]]-a[i]);
			t1--;
		}
		while (h2<=t2 && a[q2[t2]]<=a[i]){
			add(1,1,n,q2[t2-1]+1,q2[t2],-a[q2[t2]]+a[i]);
			t2--;
		}
		q1[++t1]=q2[++t2]=i;
		if (st<=i-1)
			add(1,1,n,st,i-1,-D);
		change0(1,1,n,i);
		int l=query(1,1,n,i);
		if (i-l+1>len)
			len=i-l+1,ansl=l;
		else if (i-l+1==len && l<ansl)
			ansl=l;
	}
	printf("%d %d\n",ansl,ansl+len-1);
	return 0;
}

总结

单调队列可以干的东西,可不仅仅是DP啊……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值