! AHOI/HNOI2018转盘


线段树维护单调栈

又是一道非常棒的思维题!!!


SOL:

问题转化(倒着思考)

t时刻在某点,每次可以向后走一步或留在原地,然后t减1

每个点在(T_i)消失,求最小的(t)使得所有点都可以在消失前被访问

于是惊奇地发现留在原地一定不优,会一直往前走

破环为链(2倍),对于(iin[n,2n))走到j的时间为(t-(i-j))

(jin(i-n])满足(t-(i-j)>=T_j o t_{min}=max(T_j-j)+i)

(ans=min_{iin[n,2n)}(max_{jin(i-n,i]}a_j+i),a_j=T_j-j)

用i代替i+n-1

(ans=min_{iin[1,n])}(max_{jin[i,2n]}a_j+i)+n-1)因为(a_i>a_{i+n})

(a_j)为后缀最大值,i为j前第一个比(a_j)大的位置,(ans=a_j+i+n)

需要维护一个单调栈

线段树维护单调栈——楼房重建

每个区间记录最大值及单调栈长度,合并时左边肯定都是单调栈的内容,右边二分一个位置恰好大于左边分最大值则剩余的加入单调栈,时间复杂度(O(nlog_n^2))

(p_0,p_1,…,p_k)是栈里留下的元素

(p_{j-1}<n<=p_j)

(ans=min_{iin[1,j]}(a_{p_i}+p_{i-1})+n)

因为((n,2n])里的最大值是([1,n])里的最大值-n,所以只用维护([1,n])

(mx[p])为区间最大值,(t[p])为区间左半到中间的最小答案

(ans=query(1,1,n,mx[1]-n))

时间复杂度(O(nlog_n^2))

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int x=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	return f==1?x:-x;
}
const int N=1e5+4,inf=1e9;
int n,m,op,ans,a[N],mx[N<<2],t[N<<2];
#define lc (p<<1)
#define rc (p<<1|1)
int query(int p,int l,int r,int x){
	if(l==r)return mx[p]>x?x+l:inf;
	int mid=l+r>>1;
	return mx[rc]>x?min(t[p],query(rc,mid+1,r,x)):query(lc,l,mid,x);
}
inline void pushup(int p,int l,int r){
	mx[p]=max(mx[lc],mx[rc]);
	t[p]=query(lc,l,r,mx[rc]);
}
void build(int p,int l,int r){
	if(l==r){
		mx[p]=a[l]-l;
		return;
	}
	int mid=l+r>>1;
	build(lc,l,mid);
	build(rc,mid+1,r);
	pushup(p,l,mid);
}
void modify(int p,int l,int r,int x,int y){
	if(l==r){
		mx[p]=y-x;
		return;
	}
	int mid=l+r>>1;
	if(x<=mid)modify(lc,l,mid,x,y);
	else modify(rc,mid+1,r,x,y);
	pushup(p,l,mid);
}
int main(){
	n=read();m=read();op=read();
	for(int i=1;i<=n;i++)a[i]=read();
	build(1,1,n);
	ans=query(1,1,n,mx[1]-n)+n;
	cout<<ans<<"
";
	while(m--){
		static int x,y;
		x=read();y=read();
		if(op){x^=ans;y^=ans;}
		modify(1,1,n,x,y);
		ans=query(1,1,n,mx[1]-n)+n;
		cout<<ans<<"
";
	}
	return (0-0);
}
原文地址:https://www.cnblogs.com/aurora2004/p/12604395.html