Arc077_E Guruguru

传送门

题目大意

有$m$个点编号从小到大按照顺时针编成了一个环,有一枚棋子,每次移动可以选择顺时针移动到下一个或者直接移动到编号为$x$的点,现在有$n-1$次数操作,第$i$次要把棋子从第$A_i$移到第$A_{i+1}$号节点,可以在初始时自由设定$x$,求每次操作移动步数之和的最小值。

题解

$x$对一次移动有意义当且仅当$x$被直接操作经过不在起点上,其中贡献为$起点到终点的距离$+1$-$x$到终点的距离$。

考虑设$x$能减少多少代价,维护$x$取每一个位置时能使总代价减少多少,对于一段操作区间,当$x=l+1,x=l+2...x=r$的差异恰好是一段等差数列,首相为$0$,公差为$1$,可以使用二次差分解决,最后只需要枚举每一个位置记答案最值即可。

复杂度$O(n+m)$。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define M 200020
using namespace std;
namespace IO{
	const int BS=(1<<21)+3; LL Top=0; char Buffer[BS],*HD,*TL,SS[20];
	char Getchar(){if(HD==TL){TL=(HD=Buffer)+fread(Buffer,1,BS,stdin);} return (HD==TL)?EOF:*HD++;}
	int read(){
		int nm=0; char cw=Getchar(); for(;!isdigit(cw);cw=Getchar());
		for(;isdigit(cw);cw=Getchar()) nm=nm*10+(cw-'0'); return nm;
	}
} using namespace IO;
int n,m,dt[M]; LL tot,s[M],ans;
int main(){
	n=read(),m=read();
	for(register int last=read(),i=1,x;i<n;last=x,i++){
		x=read(),dt[last+2]++;
		if(last<x) dt[x+1]--,tot+=x-last,s[x+1]-=x-last-1;
		else tot+=x+m-last,dt[1]++,dt[x+1]--,s[1]+=m-last-1,s[x+1]-=m+x-last-1;
	} ans=tot;
	for(register int i=1;i<=m;i++) dt[i]+=dt[i-1],s[i]+=s[i-1]+dt[i],ans=min(ans,tot-s[i]);
	printf("%lld
",ans); return 0;
}
原文地址:https://www.cnblogs.com/OYJason/p/9837032.html