AtCoder Regular Contest 077 E - guruguru
https://arc077.contest.atcoder.jp/tasks/arc077_c
有m个点围成一个圈,按顺时针编号为1到m,一开始可以固定一个位置x,每次操作可以往顺时针方向走一步或直接走到x。现在给出n个位置a[1..n],初始时在a[1],第i次要从a[i]走到a[i+1],在x可以任意选择的情况下使总步数最小。
对于从a走到b来说
若选择的x=a 或 a+1,那么不会使步数减少
若选择的x=a+2,会使步数减少1
若选择的x=a+3,会使步数减少2
……
问题就变成了 给区间[l,r]加首项为1,公差为1的等差数列
给位置l 加1,位置l+1加2,位置l+2加3……位置r加r-l+1
那么令cnt[l]加1,cnt[r+1]减r-l+1+1,cnt[r+2]减r-l+1
对cnt做一遍前缀和,得到差分数组
在做一遍前缀和,可以得到本身的值
据说这个叫二阶差分
两遍前缀和后的cnt数组就是把位置选在x,会使原本的步数减少多少
取最大的一个,总步数减它就是答案
#include<cstdio> #include<iostream> using namespace std; #define N 100001 int a[N]; long long cnt[N<<1]; void read(int &x) { x=0; char c=getchar(); while(!isdigit(c)) c=getchar(); while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); } } int main() { int n,m; read(n); read(m); for(int i=1;i<=n;++i) read(a[i]); int l,r; long long tot=0; for(int i=1;i<n;++i) { l=a[i]; r=a[i+1]; if(l>r) r+=m; tot+=r-l; if(r-l>1) { cnt[l+2]++; cnt[r+1]-=r-(l+2)+2; cnt[r+2]+=r-(l+2)+1; } } for(int i=1;i<=m*2;++i) cnt[i]+=cnt[i-1]; for(int i=1;i<=m*2;++i) cnt[i]+=cnt[i-1]; long long ans=tot; for(int i=1;i<=m;++i) ans=min(ans,tot-cnt[i]-cnt[i+m]); cout<<ans; }