P4425 [HNOI/AHOI2018]转盘 线段树维护单调栈

题意:

戳这里

分析:

  • 暴力

    只会 \(40pts\) 的暴力还让我写成了 \(30pts\) ,首先我们会在链上处理这个问题,\(f_i=max(t_i,f_{i-1}+1)\)

    转化一下式子

    \(f_i-i=max(t_i-i,f_{i-1}-(i-1))\)

    我们记 \(A_i=f_i-i\)

    \(\therefore A_i=max(t_i-i,A_{i-1})\)

    \(\ f_i=A_i+i=max(t_j-j)+i\)

    所以采取倍增然后断环为链的方式,维护一下每一个长度为 \(n\) 的区间里面 \(t_j-j\) 的最大值,用单调队列可以做到总复杂度 \(O(mn)\) 的然后我一看见单点修改,区间查询 想都没想直接上线段树,凭空多出来一个 \(\log\)


  • 正解:

    我们接着上面的式子

    \(ans=\displaystyle \min_{i=1}^n \{\max_{j=0}^{n-1}\{t_{i+j}-j\}+n-1\}\)

    更改一下 \(j\) 的枚举得到

    \(ans=\displaystyle \min_{i=1}^n \{\max_{j=i}^{n+i-1}\{t_j-(j-i)\}+n-1\}\)

    \(ans=\displaystyle \min_{i=1}^n \{\max_{j=i}^{n+i-1}\{t_j-j\}+n+i-1\}\)

    然后关键的一步来了

    \(\because t_i-i>t_{i+n}-(i+n)\) \(\therefore [i,n+i-1]\) 的最大值 等价于 \([i,2n]\) 的最大值,也就是说我们不需要维护区间最值了,直接维护全局的后缀最值就可以了

    虽然这种转换目前来看似乎不能直接优化复杂度

    但是,我们接着考虑,每一个固定的 \(t_j-j\) 对应一段固定后缀,那么对于 \(j\) ,它取得最优值(即 \(max(t_j-j+i\) ) 的地方是 \(i\) 满足 \(t_{i-1}-(i-1)>t_j-j\) 也就是在它前面第一个比它大的元素的后一位 ,这也就告诉我们只需要维护一个类似单调栈的结构,就能直接求出每一段最优值,但是我们需要这个单调栈支持动态修改,做过 楼房重建 的巨佬就会发现,这道题就是线段树维护单调栈

    放到这个题上具体来说就是:

    建一颗线段树,每一个节点维护一个 \(mx,val\) 其中 \(mx\) 表示区间 \(t_i-i\) 的最大值, \(val\) 表示区间 \((t_j-j)+i\) 的最小值 ( 这个 \(val\) 就相当于单调栈中一个元素,和它前面第一个比他大的元素的下一位的下标之和)

    每一次 \(pushup\) 的时候,对于 \(mx\) 直接区间取 \(max\) ,而对于 \(val\) 要进行二分查找,这个区间的 \(val\) 就是用右区间的 \(mx\) 在左区间中找到一个位置 \(i\) 满足 \(i\)\(t_i-i\) 小于 \(mx\) 的下标最小的值,查找的时候顺便和子区间的 \(val\) 取一下 \(min\)

    总复杂度 \(O(m\log ^2)\) 线段树上二分两只 \(\ log\)

代码:

#include<bits/stdc++.h>
#define pii pair<int,int>
#define mk(x,y) make_pair(x,y)
#define lc rt<<1
#define rc rt<<1|1
#define pb push_back
#define fir first
#define sec second
#define inl inline
#define reg register

using namespace std;

namespace zzc
{
	inline int read()
	{
		int x=0,f=1;char ch=getchar();
		while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
		while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
		return x*f;
	}
	
	const int maxn = 3e5+5;
	int n,m,lst,p;
	int mx[maxn<<2],val[maxn<<2],t[maxn<<1];
	
	int query(int rt,int l,int r,int x)
	{
		if(l==r) return l+max(x,mx[rt]);
		int mid=(l+r)>>1;
		if(mx[rc]<=x) return min(mid+1+x,query(lc,l,mid,x));
		else return min(val[rt],query(rc,mid+1,r,x));
	}
	
	void pushup(int rt,int l,int r)
	{
		int mid=(l+r)>>1;
		mx[rt]=max(mx[lc],mx[rc]);
		val[rt]=query(lc,l,mid,mx[rc]);		
	}
	
	void modify(int rt,int l,int r,int pos,int x)
	{
		if(l==r)
		{
			mx[rt]=x-l;
			val[rt]=x;
			return ;
		}
		int mid=(l+r)>>1;
		if(pos<=mid) modify(lc,l,mid,pos,x);
		else modify(rc,mid+1,r,pos,x);
		pushup(rt,l,r);
	}
	
	void build(int rt,int l,int r)
	{
		if(l==r)
		{
			mx[rt]=t[l]-l;
			val[rt]=t[l];
			return ;
		}
		int mid=(l+r)>>1;
		build(lc,l,mid);build(rc,mid+1,r);
		pushup(rt,l,r);
	}
	
	void work()
	{
		int x,y;
		n=read();m=read();p=read();
		for(int i=1;i<=n;i++) t[i]=t[i+n]=read();
		build(1,1,2*n);
		printf("%d\n",lst=val[1]+n-1);
		while(m--)
		{
			x=read()^(lst*p);y=read()^(lst*p);
			modify(1,1,2*n,x,y);
			modify(1,1,2*n,x+n,y);
			printf("%d\n",lst=val[1]+n-1);
		}
	}

}

int main()
{
	zzc::work();
	return 0;
}

posted @ 2021-01-15 15:08  youth518  阅读(88)  评论(0编辑  收藏  举报