CF407E k-d-sequence 数列分块或线段树+单调栈

11 篇文章 0 订阅
10 篇文章 0 订阅

原题:http://codeforces.com/contest/407/problem/E

题解:求添加k个元素后最长的等d差序列的左右端点,首先答案应该是连续的%d余数相同的一段(注意会有负数),而且这一段不可能有相同的数。对于这样的一段,将它除d化成公差为1的数列,在max{a[l-r]}-min{a[l-r]}-r<=k-l 时是等差数列。对于这样的问题考虑倒序枚举左端点,令mm[r]为i-r的最大值,mn[r]为i-r的最小值。mm[r]肯定是递增的,mn[r]肯定是递减的,考虑从l-> l-1,mm[r]显然是将小于他的一段改成a[l-1],可以用单调栈维护比他小的坐标,区间修改,mn[]也一样,用数据结构维护max{a[l-r]}-min{a[l-r]}-r,查询时要找到右符合条件的。数据结构可以用数列分块,简单,直接,好写。

需要注意的细节

1.特判 d==0 不然会re

2.注意有负数

3.判重的思想,若有r1<r2且a[r1]==a[r2],对于当前的 l 有效区间为[l,r2-1],用数较大用map维护相同的最左的坐标。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<cmath>
using namespace std;
const int N=2e5+10;
const int M=1000000000;
const int INF=1e18; 
int a[N],b[N],s1[N],s2[N];
int n,m,k,d,top1,top2,ansr,ansl,sum[N],pos[N],t[N],mn[N];
map<int,int>mp;
void reset(int x){
	mn[x]=INF;
	for(int i=(x-1)*m+1;i<=min(n,x*m);i++) mn[x]=min(mn[x],t[i]+sum[x]); 
}
inline void add(int l,int r,int c){
	for(int i=l;i<=min(r,pos[l]*m);i++) t[i]+=c;
	reset(pos[l]);
	if(pos[l]!=pos[r]){
		for(int i=(pos[r]-1)*m+1;i<=min(n,r);i++) t[i]+=c; 
		reset(pos[r]);
	}
	for(int i=pos[l]+1;i<=pos[r]-1;i++) sum[i]+=c,mn[i]+=c;
}
inline int query(int l,int r,int c){
	for(int i=r;i>=max((pos[r]-1)*m+1,l);i--)
		if(t[i]+sum[pos[i]]<=c){ return i;}
		
	for(int i=pos[r]-1;i>=pos[l]+1;i--) 
		if(mn[i]<=c)
			for(int j=i*m;j>=(i-1)*m+1;j--) 
				if(t[j]+sum[pos[j]]<=c) {return j;}
	if(pos[l]!=pos[r]){
		for(int i=pos[l]*m;i>=l;i--) {
			if(t[i]+sum[pos[i]]<=c)return i;
		}
	}
}
inline void sol(int L,int R){
	if(L==R) return ;
	mp.clear();int r=R+1;
	top1=top2=0;s1[top1]=s2[top2]=R+1;
	for(int i=0;i<=pos[n];i++) sum[i]=0;
	for(int i=R;i>=L;i--){
		if(mp.count(a[i])) r=min(r,mp[a[i]]);
		while(top1 && a[i]>a[s1[top1]]){	 
			add(s1[top1],s1[top1-1]-1,-a[s1[top1]]);
			top1--;
		} 
		s1[++top1]=i;
		add(s1[top1],s1[top1-1]-1,a[s1[top1]]);
		while(top2 && a[i]<a[s2[top2]]){
			add(s2[top2],s2[top2-1]-1,a[s2[top2]]);
			top2--;	
		}
		s2[++top2]=i;
		add(s2[top2],s2[top2-1]-1,-a[s2[top2]]);
		int rr=query(i,r-1,k-i);
		if(rr-i>ansr-ansl || (rr-i==ansr-ansl && i<ansl) ) ansl=i,ansr=rr;
		mp[a[i]]=i;
	}
	return ;
}
int main(){
	scanf("%d%d%d",&n,&k,&d);
	if(d==0){
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		int ansl=1;int ansr=1;int lst=1;
		for(int i=2;i<=n;i++){
			if(a[i]!=a[lst]){
				if(i-1-lst>ansr-ansl){ansl=lst;ansr=i-1;}
				lst=i;
			}
		}
		printf("%d %d\n",ansl,ansr);
		return 0;
	}
	ansl=n+1;ansr=0;m=sqrt(n);
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]);
		b[i]=(a[i]+=M)%d;a[i]=(a[i])/d+1;
		pos[i]=(i-1)/m+1;t[i]=-i;
	}
	for(int i=1,j;i<=n;i=j){
		for(j=i+1;j<=n;j++){
			if(b[i]!=b[j]) break;	
		}
		sol(i,j-1);
	}
	printf("%d %d\n",ansl,ansr); 
	return 0;
} 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值