• BZOJ5286 HNOI/AHOI2018转盘(分块/线段树)


      显然最优走法是先一直停在初始位置然后一次性走完一圈。将序列倍长后,相当于找一个长度为n的区间[l,l+n),使其中ti+l+n-1-i的最大值最小。容易发现ti-i>ti+n-(i+n),所以也就相当于是后缀最大值最小。设ti-i=ai,即要求min{l+max{al..2n}}+n-1 (l=1..n)。如果没有修改的话只要扫一遍就行了。

      线段树看起来很难维护,考虑分块。每一块求出仅考虑该块的ai时上述值的前缀min和ai的后缀max。对于查询,从后往前考虑所选区间左端点在哪一块。如果该块某个位置出现了整个序列的后缀最大值,序列后面的部分就不会对该块之前位置的答案产生影响,可以直接使用之前求出的答案。于是根据后缀最大值将该块划分成两部分,后一部分由于max{ai}被固定为后缀最大值,当然选择尽量左的点时最优。修改时暴力重构块即可。块大小取sqrt(nlogn)时最优,为O(msqrt(nlogn))。没有卡常也在慢如狗的bzoj上只跑了11s。

    #include<iostream> 
    #include<cstdio>
    #include<cmath>
    #include<cstdlib>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    #define ll long long
    #define N 200010
    #define inf 2000000010
    char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;}
    int gcd(int n,int m){return m==0?n:gcd(m,n%m);}
    int read()
    {
        int x=0,f=1;char c=getchar();
        while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
        while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
        return x*f;
    }
    int n,m,op,a[N],L[N],R[N],pos[N],mx[N],mn[N],block,num,ans=inf;
    void build(int x)
    {
        mx[R[x]]=a[R[x]];mn[R[x]]=R[x]+a[R[x]];
        for (int i=R[x]-1;i>=L[x];i--)
        mn[i]=i+(mx[i]=max(mx[i+1],a[i]));
        for (int i=L[x]+1;i<=R[x];i++)
        mn[i]=min(mn[i-1],mn[i]);
    }
    int query()
    {
        int u=-inf,ans=inf;
        for (int i=2*num;i>num;i--) u=max(u,mx[L[i]]);
        for (int i=num;i>=1;i--)
        {
            int l=L[i],r=R[i],x=R[i]+1;
            while (l<=r)
            {
                int mid=l+r>>1;
                if (mx[mid]<=u) x=mid,r=mid-1;
                else l=mid+1;
            }
            ans=min(ans,x+u);if (x>L[i]) ans=min(ans,mn[x-1]);
            u=max(u,mx[L[i]]);
        }
        return ans;
    }
    int main()
    {
    #ifndef ONLINE_JUDGE
        freopen("bzoj5286.in","r",stdin);
        freopen("bzoj5286.out","w",stdout);
        const char LL[]="%I64d
    ";
    #else
        const char LL[]="%lld
    ";
    #endif
        n=read(),m=read(),op=read();block=sqrt(n*log(n+1));num=(n-1)/block+1;
        for (int i=1;i<=n;i++) a[i]=a[i+n]=read();
        for (int i=1;i<=n*2;i++) a[i]-=i;
        for (int i=1;i<=num;i++)
        {
            L[i]=R[i-1]+1,R[i]=min(n,L[i]+block-1);
            for (int j=L[i];j<=R[i];j++)
            pos[j]=i;
            build(i);
        }
        for (int i=num+1;i<=2*num;i++)
        {
            L[i]=R[i-1]+1,R[i]=min(2*n,L[i]+block-1);
            for (int j=L[i];j<=R[i];j++)
            pos[j]=i;
            build(i);
        }
        ans=query()+n-1;cout<<ans<<endl;
        while (m--)
        {
            int x=read()^ans*op,y=read()^ans*op;
            a[x]=y-x,a[x+n]=y-x-n;build(pos[x]),build(pos[x+n]);
            printf("%d
    ",ans=query()+n-1);
        }
        return 0;
    }

      事实上这个做法可以扩展到线段树上(其实完全没有任何相似的地方吧?)。考虑每个节点维护该区间的最大值和仅考虑该区间ai时左半部分的最优答案。只要解决如何合并两个区间就可以了。类似的,右边的区间可以直接返回答案,然后考虑左节点的右半部分和右节点最大值的大小,如果左节点较大,直接返回左节点的左半部分答案并递归右节点,否则递归左节点。于是复杂度即为O(nlog2n)。鬼知道我在干什么莫名其妙写了一晚上。

    #include<iostream> 
    #include<cstdio>
    #include<cmath>
    #include<cstdlib>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    #define ll long long
    #define N 200010
    #define inf 2000000010
    char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;}
    int gcd(int n,int m){return m==0?n:gcd(m,n%m);}
    int read()
    {
        int x=0,f=1;char c=getchar();
        while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
        while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
        return x*f;
    }
    int n,m,op,a[N],ans=inf;
    struct data{int l,r,max,ans;
    }tree[N<<2];
    int query(int k,int mx)
    {
        if (tree[k].l==tree[k].r) return tree[k].l+max(mx,tree[k].max);
        if (tree[k<<1|1].max>=mx) return min(tree[k].ans,query(k<<1|1,mx));
        else return min((tree[k].l+tree[k].r>>1)+1+mx,query(k<<1,mx));
    }
    void build(int k,int l,int r)
    {
        tree[k].l=l,tree[k].r=r;
        if (l==r) {tree[k].max=a[l];return;}
        int mid=l+r>>1;
        build(k<<1,l,mid);
        build(k<<1|1,mid+1,r);
        tree[k].max=max(tree[k<<1].max,tree[k<<1|1].max);
        tree[k].ans=query(k<<1,tree[k<<1|1].max);
    }
    void modify(int k,int p,int x)
    {
        if (tree[k].l==tree[k].r) {tree[k].max=x;return;}
        int mid=tree[k].l+tree[k].r>>1;
        if (p<=mid) modify(k<<1,p,x);
        else modify(k<<1|1,p,x);
        tree[k].max=max(tree[k<<1].max,tree[k<<1|1].max);
        tree[k].ans=query(k<<1,tree[k<<1|1].max);
    }
    int main()
    {
    #ifndef ONLINE_JUDGE
        freopen("bzoj5286.in","r",stdin);
        freopen("bzoj5286.out","w",stdout);
        const char LL[]="%I64d
    ";
    #else
        const char LL[]="%lld
    ";
    #endif
        n=read(),m=read(),op=read();
        for (int i=1;i<=n;i++) a[i]=a[i+n]=read();
        for (int i=1;i<=n*2;i++) a[i]-=i;
        build(1,1,2*n);
        ans=tree[1].ans+n-1;cout<<ans<<endl;
        while (m--)
        {
            int x=read()^ans*op,y=read()^ans*op;
            modify(1,x,y-x),modify(1,x+n,y-x-n);
            printf("%d
    ",ans=tree[1].ans+n-1);
        }
        return 0;
    }
  • 相关阅读:
    数据库
    数据库
    数据库
    数据库
    数据库
    数据库
    windows
    LeetCode : Word Pattern
    LeetCode : Perfect Number
    LeetCode : Minimum Depth of Binary Tree
  • 原文地址:https://www.cnblogs.com/Gloid/p/10113368.html
Copyright © 2020-2023  润新知