[HNOI/AHOI2018]转盘(线段树优化单调)

时间:2023-03-10 00:11:15
[HNOI/AHOI2018]转盘(线段树优化单调)

[HNOI/AHOI2018]转盘(线段树优化单调)

gugu  bz

lei了lei了,事独流体毒瘤题

一句话题意:任选一个点开始,每个时刻向前走一步或者站着不动

问实现每一个点都在$T_i$之后被访问到的最短时间

Step 1

该题可证:

最优方案必然是从某一格开始后一直等着然后走一圈正好全部访问到

证明:

如果时间倒流那就是从时刻T开始每一时刻向前走或是停着不动,每个元素过了T[i]会消失

如果这样的话你肯定是马不停蹄往前走对吧

所以反过来就是上面的结论

Q.E.D

上面的结论还有其他正确的表示方法,该方法仅供参考

如果您能想出其他证明,那么请让我Orz您

Step 2

根据上述结论,答案出来了$min_{i=1}^{n}(\max_{j=i}^{i+n-1}(T_j-j+i))+n-1$

暴力胡写都有$n^2$20分,可惜我考场上直接放弃

介个是用线段树优化的说...

简单思路:$T_j-j$可以单独提出来设为$f_j$

原式可变成$\min_{i=1}^{n}(\max_{j=i}^{i+n-1}(f_j)+i)+n-1$

做这道题之前我也没见过log的pushup

随手找了点参考文献(???)[luoguP4198]楼房重建

事实上并没有什么关系,但是确实十分相似

线段树维护方法都是差不多的,随便yy应该就差不多

代码:

 #include<cstdio>
#include<algorithm>
using std::max;
using std::min;
const int N=;
int n,T,plas;
int v[N],f[N];
struct segtree
{
int ma[N<<],ans[N<<];
int query(int vi,int px,int pl,int pr)
{
if(pl==pr) return pl+max(ma[px],vi);
int mid=(pl+pr)>>;
if(ma[px<<|]>vi) return min(ans[px],query(vi,px<<|,mid+,pr));
else return min(mid++vi,query(vi,px<<,pl,mid));
}
void fuckup(int px,int pl,int pr)
{
ma[px]=max(ma[px<<],ma[px<<|]);
ans[px]=query(ma[px<<|],px<<,pl,(pl+pr)>>);
}
void add(int x,int px,int pl,int pr)
{
if(pl==pr)
{
ma[px]=f[x],ans[px]=v[x];
return;
}
int mid=(pl+pr)>>;
if(x<=mid) add(x,px<<,pl,mid);
else add(x,px<<|,mid+,pr);
fuckup(px,pl,pr);
}
int get()
{
return ans[]+n-;
}
}rk;
int xin,vin; int lastans;
int main()
{
scanf("%d%d%d",&n,&T,&plas);
for(int i=;i<=n;i++) scanf("%d",&v[i]),v[i+n]=v[i],f[i]=v[i]-i,f[i+n]=v[i+n]-i-n;
for(int i=;i<=n<<;i++) rk.add(i,,,n<<);
lastans=rk.get();
printf("%d\n",lastans);
if(!plas) lastans=;
while(T--)
{
scanf("%d%d",&xin,&vin);
xin^=lastans,vin^=lastans;
v[xin]=vin,v[xin+n]=vin,f[xin]=v[xin]-xin,f[xin+n]=v[xin+n]-xin-n;
rk.add(xin,,,n<<),rk.add(xin+n,,,n<<);
lastans=rk.get();
printf("%d\n",lastans);
if(!plas) lastans=;
}
return ;
}

巨佬您txdy