一、题目
二、解法
考虑原数组的差分数组 \(c_i\),那么操作变成任意选取差分数组的两个位置,把其中一个增加 \(a/b\),把另一个减少 \(a/b\),要求最后把差分数组变成全 \(0\) 的最小步数。
单独考虑每个位置,是一个二元一次方程的形式:\(ax_i+by_i=c_i\),我们想要让 \(|x_i|+|y_i|\) 最小,只需要讨论 \(x_i/y_i\) 是 \(0\) 右侧的最小值 \(/\) \(0\) 左侧的最大值这四种情况。
但是这样忽略了全局的限制 \(\sum x_i=0\),考虑调整法,如果 \(\sum x_i>0\),那么我们找到一个代价最小的位置,让它的 \(x_i\) 减少 \(\frac{b}{\gcd (a,b)}\),\(y_i\) 增加 \(\frac{a}{\gcd(a,b)}\),循环这个过程 \(\frac{\sum x_i}{b'}\) 次就得到了最优解(\(\sum x_i<0\) 同理)
这样做为什么是对的呢?因为每个位置的代价关于调整次数是单增的,所以贪心选取代价最小的位置就是最优的。
可以证明 \(|\sum x_i|\leq nb'\),这说明调整次数不会超过 \(n\) 次,所以时间复杂度 \(O(n\log n)\)
#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
const int M = 100005;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,a,b,w[M],sx[M],sy[M];
struct node
{
int x,c;
bool operator < (const node &b) const
{return c>b.c;}
};priority_queue<node> q;
int Abs(int x) {return x>0?x:-x;}
int f(int i)
{
return Abs(sx[i]-b)+Abs(sy[i]+a)
-Abs(sx[i])-Abs(sy[i]);
}
int exgcd(int a,int b,int &x,int &y)
{
if(!b) {x=1;y=0;return a;}
int d=exgcd(b,a%b,y,x);
y-=x*(a/b);return d;
}
signed main()
{
n=read();a=read();b=read();
for(int i=1;i<=n;i++) w[i]=read();
for(int i=++n;i;i--) w[i]=w[i]-w[i-1];
int x=0,y=0,d=exgcd(a,b,x,y);a/=d;b/=d;
for(int i=1;i<=n;i++)
{
int c=w[i];
if(c%d) {puts("-1");return 0;}
int X=((x*c/d)%b+b)%b,Y=(c/d-a*X)/b;
sx[i]=X;sy[i]=Y;
//
X-=b;Y+=a;
if(Abs(X)+Abs(Y)<Abs(sx[i])+Abs(sy[i]))
sx[i]=X,sy[i]=Y;
//
Y=((y*c/d)%a+a)%a;X=(c/d-Y*b)/a;
if(Abs(X)+Abs(Y)<Abs(sx[i])+Abs(sy[i]))
sx[i]=X,sy[i]=Y;
//
Y-=a;X+=b;
if(Abs(X)+Abs(Y)<Abs(sx[i])+Abs(sy[i]))
sx[i]=X,sy[i]=Y;
}
int ans=0,s=0;
for(int i=1;i<=n;i++) s+=sx[i];s/=b;
if(s<0) s=-s,swap(a,b),swap(sx,sy);
for(int i=1;i<=n;i++) q.push({i,f(i)});
while(s--)
{
int x=q.top().x;q.pop();
sx[x]-=b;sy[x]+=a;
q.push({x,f(x)});
}
for(int i=1;i<=n;i++)
ans+=Abs(sx[i])+Abs(sy[i]);
printf("%lld\n",ans/2);
}