显然最优走法是先一直停在初始位置然后一次性走完一圈。将序列倍长后,相当于找一个长度为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; }