题解 [POI2012] Leveling Ground
Decsription
给定一个长度为 \(n\) 的数组,每次操作可以将一个区间的数增加 \(a\) 或减少 \(b\) ,或将一个区间的数增加 \(b\) 或减少 \(a\)。求使整个数组变为 \(0\) 的最小操作次数。若无解请输出 \(-1\)。
Solution
一道很有意思的思维题。不看题解根本不会。
看到区间加减一个数的操作,我们可以想到转化为差分数组上单点加减。
我们看到 \(a\),\(b\) 不难联想到这样一个方程 \(ax+by=c\)。
是的,我们可以通过exgcd来解决这个问题。
无解的情况很显然:当存在 \(c\not \mid \gcd(a,b)\) 时,方程无解。(裴蜀定理)
我们便找到了一组通解。
考虑最小化操作次数 \(|x|+|y|\),不难发现它只有一下几种可能:
-
\(x\) 为最小非负数,
-
\(x\) 为最大非正数
-
\(y\) 为最小非负数
-
\(y\) 为最大非正数
我们又知道通解 \(x=x_0+k\times\frac b {\gcd(a,b)}\ \ y=y_0+k\times \frac a {\gcd(a,b)}\)。
所以不难找到这四种解。
但我们要注意全局的合法性,我们在差分时选择了两个数,一个加,一个减。
所以我们总的 \(\sum\mathrm{sign(a)}=0\),对于 \(b\) 也是同理。
显然,我们刚才的贪心并不满足这个性质,所以我们采用反悔堆。
注意到 \(x\) 和 \(y\) 具有方程关系,所以只要 \(x\) 满足条件 \(y\) 也一定满足。
我们找出 \(x,y\) 中操作总和 \(>0\) 的一类,考虑如何使它变为 \(0\)。
(满足了 \(ax+by=c\),故一正一负。)令 \(\mathrm{cnt_x}>\mathrm{cnt_y}\),
通过上述通解,我们可以尝试每次将 \(x\) 减少 \(\frac a {gcd(a,b)}\),\(y\) 增加 \(\frac b {\gcd(a.b)}\)。
用大根堆维护代价最大的一组,每次贪心的选它。
再吧反悔的 \(now-last\) 加入就好。
在 \(\sum x=0\) 时停止即可。
Code:
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
#define int long long
#define PII pair<int,int>
const int N=100009;
int n,a,b,Ans;
int A[N],X[N],Y[N];
priority_queue<PII> Q;
int exgcd(int a,int b,int &x,int &y)
{
if(!b){x=1,y=0;return a;}
int GCD=exgcd(b,a%b,x,y);
int z=x;x=y,y=z-a/b*y;
return GCD;
}
inline bool pd(int x,int y,int X,int Y)
{
return abs(x)+abs(y)<abs(X)+abs(Y);
}
signed main()
{
scanf("%lld%lld%lld",&n,&a,&b);
for(int i=1;i<=n;i++) scanf("%lld",&A[i]);
for(int i=n+1;i>=1;i--) A[i]=A[i]-A[i-1];++n;
int x,y,d=exgcd(a,b,x,y);a/=d,b/=d;//exgcd特解
for(int i=1,xx,yy;i<=n;i++)
{
if(A[i]%d!=0) return puts("-1"),0;
xx=X[i]=(x*A[i]/d%b+b)%b;
yy=Y[i]=(A[i]/d-xx*a)/b;
xx-=b,yy+=a;
if(pd(xx,yy,X[i],Y[i])) X[i]=xx,Y[i]=yy;
yy=(y*A[i]/d%a+a)%a;
xx=(A[i]/d-yy*b)/a;
if(pd(xx,yy,X[i],Y[i])) X[i]=xx,Y[i]=yy;
yy-=a,xx+=b;
if(pd(xx,yy,X[i],Y[i])) X[i]=xx,Y[i]=yy;
}
int rest=0;
for(int i=1;i<=n;i++) rest+=X[i];
rest/=b;
if(rest<0) rest=-rest,swap(a,b),swap(X,Y);
for(int i=1;i<=n;i++) Q.push( make_pair(-(abs(X[i]-b)+abs(Y[i]+a)-abs(X[i])-abs(Y[i])) ,i));
while(rest--)
{
int u=Q.top().second;Q.pop();
X[u]-=b,Y[u]+=a;
Q.push( make_pair(-(abs(X[u]-b)+abs(Y[u]+a)-abs(X[u])-abs(Y[u])) ,u));
}
for(int i=1;i<=n;i++) Ans+=abs(X[i])+abs(Y[i]);
printf("%lld",Ans>>1);
return 0;
}