2800: [Poi2012]Leveling Ground

将x[i]数组差分,此题变为挑选两个数,分别加a或b、减a或b。
为了方便,先将a、b与每个x[i]都除以gcd(a,b),如果不能整除则无解。
然后exgcd,找出a*u[i]+b*v[i]=x[i],然后对这个数加u[i]个a,v[i]个b就好辣!
选择任意一组解记为u0[i],v0[i]
则u[i]=u0[i]+k*b,v[i]=v0[i]-k*a
使答案最优,要让每个|u[i]|+|v[i]|尽量小,即|u0[i]+k*b|+|v0[i]-k*a|,记为ans[i]
发现这是两个关于k的绝对值一次函数的和,必为单谷函数,因此三分求出使其最小的k值。
但是这样的解不一定合法,即满足∑u[i]=∑v[i]=0
因此我们需要调整k的值使其合法。注意到a*∑u[i]+b*∑v[i]=∑x[i]=0(差分的性质)
因此只要调整∑u[i]=0就好了。
即∑(u0[i]+k*b)=0。所以要将∑k调小(∑u[i])/b
把k减小1后的Δans[i]压进一个小根堆里,每次取堆顶更新。
然后就得到了最优的u[]和v[],答案即为(∑u[i]+∑v[i])/2。
注意a==b时要特判! 别问为什么,因为我就卡这里了!
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
#define rep(i,j,k) for(i=j;i<=k;++i)
#define per(i,j,k) for(i=j;i>=k;--i)
#define sqr(x) ((x)*(x))
#define G getchar()
#define LL long long
#define pll pair<LL,LL>
#define mkp make_pair
#define X first
#define Y second
#define N 100005  
#define inf 1000000001LL
LL n,d[N],A,B,U[N],V[N],K[N],ans;
priority_queue<pll,vector<pll>,greater<pll> >pq;
LL read(){
	LL x=0;char ch=G;bool flg=0;
	for(;ch<48||ch>57;ch=G)flg|=ch==45;
	for(;ch>47&&ch<58;ch=G)x=x*10+ch-48;
	return flg?-x:x;
}
void exgcd(LL a,LL b,LL&z,LL&x,LL&y){
	if(!b){z=a;x=1;y=0;return;}
	exgcd(b,a%b,z,y,x);y-=a/b*x;
}
LL get(LL u,LL v,LL k){
	return abs(u+k*B)+abs(v-k*A);
}
LL Find(LL u,LL v,LL l,LL r){
	if(r-l<3){
		LL x=get(u,v,l),y=get(u,v,l+1),z=get(u,v,r);
		if(y<x)++l,x=y;if(z<x)l=r;return l;
	}
	LL ml=l+(r-l)/3,mr=r-(r-l)/3;
	if(get(u,v,ml)<get(u,v,mr))return Find(u,v,l,mr);
	else return Find(u,v,ml,r);
}
int main(){
	LL i,x,y,z,m=0;
	n=read();A=read();B=read();
	rep(i,1,n)d[i]=read();
	per(i,++n,2)d[i]-=d[i-1];
	exgcd(A,B,z,x,y);
	if(z<0)z=-z,x=-x,y=-y;A/=z;B/=z;
	rep(i,1,n){
		if(d[i]%z){puts("-1");return 0;}
		d[i]/=z;K[i]=Find(U[i]=x*d[i],V[i]=y*d[i],-inf,inf);
		m+=U[i]+B*K[i];
	}
	if(A==B){
		rep(i,1,n)ans+=abs(d[i]);
		printf("%d\n",ans>>1);return 0;
	}
	if(m>0){
		rep(i,1,n)pq.push(mkp(get(U[i],V[i],K[i]-1)-get(U[i],V[i],K[i]),i));
		for(m/=B;m--;){
			y=pq.top().Y;pq.pop();--K[y];
			pq.push(mkp(get(U[i],V[i],K[i]-1)-get(U[i],V[i],K[i]),i));
		}
	}
	else if(m<0){
		rep(i,1,n)pq.push(mkp(get(U[i],V[i],K[i]+1)-get(U[i],V[i],K[i]),i));
		for(m/=B;m++;){
			y=pq.top().Y;pq.pop();++K[y];
			pq.push(mkp(get(U[i],V[i],K[i]+1)-get(U[i],V[i],K[i]),i));
		}
	}
	rep(i,1,n)ans+=abs(U[i]+K[i]*B)+abs(V[i]-K[i]*A);
	printf("%lld\n",ans>>1);
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值