loj2495/bzoj5286/洛谷P4425 转盘 线段树

可以发现,如果我需要在转盘上走两圈,不如在第一个物品上停留一段时间,然后再走一圈。
假设我们固定起点,那么一定是走一圈+多出来的一点点。
会发现那多出来的一点点假设走到x,那么以x+1为起点就可以只走一圈。
所以得到结论:最优方案一定是在第一个物品上停留一段时间后走一圈

拆环,把原数列往后面粘一遍,设 ai=Tii a i = T i − i (即走到这里时可以标记该物品需要预先停留的时间),则答案为:

mini=1n{maxj=1n{ai+j1+i}}+n1 min i = 1 n { max j = 1 n { a i + j − 1 + i } } + n − 1

也就是:
mini=1n{maxj=1n{ai+j1}+i}+n1 min i = 1 n { max j = 1 n { a i + j − 1 } + i } + n − 1

以下将取 maxnj=1{ai+j1}+i max j = 1 n { a i + j − 1 } + i 的区间称作“窗口”,而左边这个取max的式子称作“窗口的答案”,注意窗口的长度不一定是n。
用线段树弄一弄。
假设线段树上一个节点代表的区间是 [s,t] [ s , t ] ,那么我们开一个数组 mx m x 表示这个区间内最大的 ai a i ,开一个数组 ans a n s 来维护所有包含了整个右半区间的窗口中 maxnj=1{ai+j1}+i max j = 1 n { a i + j − 1 } + i 的最小值。
这样维护的窗口可能长度会大于n,但是由于在整个长为2n的序列中,有 ax=Txx,ax+n=Txxn a x = T x − x , a x + n = T x − x − n 的缘故,如果这个窗口包含了 ax a x ,那么 ax+n a x + n 及以后的值并不会对该窗口的答案产生影响,所以可以视作所有的窗口长度都是小于等于n的。而每个窗口的长度一定是大于 ts+12 t − s + 1 2 的,所以线段树的根节点,也就是代表区间为 [1,2n] [ 1 , 2 n ] 的那个节点的窗口长度是大于等于n的,综上,我们可以统计所有长度为n的窗口的答案。

然后就是在线段树操作的时候,如何合并左右子区间的答案的问题了。方法和这道题差不多,具体实现看代码。

复杂度 O(nlog2n+mlog2n) O ( n l o g 2 n + m l o g 2 n )

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
    int q=0;char ch=' ';
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
    return q;
}
const int N=200005;
int n,m,p,lasans;
#define ls (i<<1)
#define rs ((i<<1)|1)
int T[N],a[N],ans[N<<2],mx[N<<2];
//orz:合并左右子区间答案
int orz(int i,int s,int t,int num) {
    if(s==t) return s+max(mx[i],num);
    int mid=(s+t)>>1;
    if(mx[rs]>=num) return min(ans[i],orz(rs,mid+1,t,num));
    //此时窗口的左端点在左半时max受的不是num影响,而是右半影响,所以取一次ans[i]
    else return min(orz(ls,s,mid,num),mid+1+num);
    //否则整个右半作窗口左端点的max都受num影响,左端点越小答案越小。
}
void up(int i,int s,int t) {
    mx[i]=max(mx[ls],mx[rs]);
    ans[i]=orz(ls,s,(s+t)>>1,mx[rs]);//既然考虑的窗口要包含整个右子区间,则一定包含其a[i]最大值
}
void build(int s,int t,int i) {
    if(s==t) {ans[i]=T[s],mx[i]=a[s];return;}
    int mid=(s+t)>>1;
    build(s,mid,ls),build(mid+1,t,rs),up(i,s,t);
}
void chan(int x,int s,int t,int i) {
    if(s==t) {ans[i]=T[s],mx[i]=a[s];return;}
    int mid=(s+t)>>1;
    if(x<=mid) chan(x,s,mid,i<<1);
    else chan(x,mid+1,t,(i<<1)|1);
    up(i,s,t);
}
int main()
{
    int x,y;
    n=read(),m=read(),p=read();
    for(RI i=1;i<=n;++i) {
        T[i]=read(),T[i+n]=T[i];
        a[i]=T[i]-i,a[i+n]=T[i+n]-i-n;
    }
    build(1,n<<1,1);
    lasans=ans[1]+n-1;printf("%d\n",lasans);
    while(m--) {
        x=read(),y=read();
        if(p) x^=lasans,y^=lasans;
        T[x]=T[x+n]=y,a[x]=y-x,a[x+n]=y-x-n;
        chan(x,1,n<<1,1),chan(x+n,1,n<<1,1);
        lasans=ans[1]+n-1;printf("%d\n",lasans);
    }
    return 0;
}

最后,鸣谢这位dalao对本蒟蒻理解本题的帮助。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值