(HN)AHOI2018 转盘

题意:

(n) 个格子围成一圈,每个格子里有一个物品,每个物品的出现时间为 (T_i) 。开始时选择一个格子为起点,每个单位时间可以向前走一格或不动,若当前格的物品已出现则将其标记。有 (m) 次修改,每次修改一个物品的出现时间,并询问将所有物品标记的最短时间。

题解:

猜一下一个最优方案:选择一个点后停留一段时间后直接走一圈标记所有物品。感性分析一下是对的,因为多走超过一圈的地方可以用等待来代替。具体分析或证明可以看其他神仙的博客。

那么得到柿子:

[ans=displaystyle min _{i=1}^n { max_{j=i}^{i+n-1} {T_j-(j-i)}}+n-1 ]

(前者为最大等待时间)。

整理一下柿子,设 (a_j=T_j-j)

[ans=displaystyle min _{i=1}^n { max_{j=i}^{i+n-1} {a_j}+i}+n-1 ]

我们发现对于 ([i+ n,2n]) 的答案显然不会更优,所以我们减少一下限制,即:

[ans=displaystyle min _{i=1}^n { max_{j=i}^{2n} {a_j}+i}+n-1 ]

发现最后对答案可能产生贡献的点的 (a_j) 一定从右到左单增。

于是我们直接用线段树维护区间最大值和 (min_{i=l}^{r/2}{max_{j=l}^{r}{a_j}+i})即可,即维护一个单调栈。时间复杂度为 (O(mlog^2 n)) .

#include<cstdio>
#include<algorithm>
using namespace std;
inline int gi()
{
	char c; int x=0;
	for(;c<'0'||c>'9';c=getchar());
	for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+c-'0';
	return x;
}
const int N=200005;
int n,m,p,ans,t[N],st[N<<2],mx[N<<2];
#define lx (x<<1)
#define rx (x<<1|1)
int query(int x, int l, int r, int w)
{
	if(l==r) return l+max(w,mx[x]);
	int mid=l+r>>1;
	if(mx[rx]>=w) return min(st[x],query(rx,mid+1,r,w));
	return min(mid+1+w,query(lx,l,mid,w));
}
void pushup(int x, int l, int r)
{
	int mid=l+r>>1;
	mx[x]=max(mx[lx],mx[rx]);
	st[x]=query(lx,l,mid,mx[rx]);
}
void build(int x, int l, int r)
{
	if(l==r)
	{
		mx[x]=t[l],st[x]=t[l]+l;
		return ;
	}
	int mid=l+r>>1;
	build(lx,l,mid),build(rx,mid+1,r);
	pushup(x,l,r);
}
void update(int x, int l, int r, int s)
{
	if(l==r)
	{
		mx[x]=t[l],st[x]=t[l]+l;
		return ;
	}
	int mid=l+r>>1;
	s<=mid?update(lx,l,mid,s):update(rx,mid+1,r,s);
	pushup(x,l,r);
}
int main()
{
#ifndef ONLINE_JUDGE
	freopen("circle.in","r",stdin);
	freopen("circle.out","w",stdout);
#endif
	n=gi(),m=gi(),p=gi();
	for(int i=1;i<=n;++i) t[i]=gi()-i,t[i+n]=t[i]-n;
	build(1,1,n<<1);
	printf("%d
",ans=st[1]+n-1);
	while(m--)
	{
		ans*=p;
		int x=gi()^ans,y=gi()^ans;
		t[x]=y-x,t[x+n]=t[x]-n;
		update(1,1,n<<1,x),update(1,1,n<<1,x+n);
		printf("%d
",ans=st[1]+n-1);
	}
}

原文地址:https://www.cnblogs.com/farway17/p/10423461.html