题解 [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;
}
posted @ 2020-10-07 20:19  Alansp  阅读(142)  评论(0编辑  收藏  举报