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<<]; void read(int &x)
{
x=; char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) { x=x*+c-''; c=getchar(); }
} int main()
{
int n,m;
read(n); read(m);
for(int i=;i<=n;++i) read(a[i]);
int l,r;
long long tot=;
for(int i=;i<n;++i)
{
l=a[i];
r=a[i+];
if(l>r) r+=m;
tot+=r-l;
if(r-l>)
{
cnt[l+]++;
cnt[r+]-=r-(l+)+;
cnt[r+]+=r-(l+)+;
}
}
for(int i=;i<=m*;++i) cnt[i]+=cnt[i-];
for(int i=;i<=m*;++i) cnt[i]+=cnt[i-];
long long ans=tot;
for(int i=;i<=m;++i) ans=min(ans,tot-cnt[i]-cnt[i+m]);
cout<<ans;
}