bzoj 2800 [Poi2012]Leveling Ground 数学

先把这个东西差分,那么就从一段区间加减变成了一个点加,另一个点减。

然后把所有数和a,b都除gcd(a,b)

然后每个点可以表示为 d[i]=x[i]a+y[i]b

用exgcd求ax+by=1的一组解。

那么一个点的次数可以表示为: |xd[i]+kb|+|yd[i]+ka|

两个绝对值一次函数套在一起是一个单峰函数,因此可以三分这个k。

不过还需要保证 xd[i]+kb=0 yd[i]+ka=0

由于 axd[i]+kb+byd[i]+ka=0

因此只需要保证 xd[i]+kb=0

求出当前的 (xd[i]+kb)/b 就是所有k的总改变量。

然后用优先队列每次找k改变一个单位 |xd[i]+kb|+|yd[i]+ka| 变化最少的元素,把它的k改变一个单位。

重复 (xd[i]+kb)/b 次。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 110000
#define PA pair<ll,int> 
int n,a,b;
int d[N];
ll pos[N],X[N],Y[N],ans,sum;
void quit(){puts("-1");exit(0);}
void exgcd(ll &x,ll &y,int a,int b)
{
    if(b==0){y=0;x=1;return;}
    exgcd(y,x,b,a%b);y-=a/b*x;
}
priority_queue<PA,vector<PA>,greater<PA> >q;
ll cal(ll x,ll y,ll v){return abs(x+v*b)+abs(y-v*a);}
int main()
{
    scanf("%d%d%d",&n,&a,&b);
    int t=__gcd(a,b);a/=t;b/=t;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&d[i]);
        if(d[i]%t)quit();
        d[i]/=t;
    }
    n++;
    if(a==b)
    {
        for(int i=n;i>=1;i--)ans+=abs(d[i]-d[i-1]);
        return printf("%lld\n",ans/2),0;
    }
    ll x,y;exgcd(x,y,a,b);
    for(int i=n;i>=1;i--)
    {
        d[i]=d[i]-d[i-1];
        X[i]=x*d[i];Y[i]=y*d[i];
        ll l=-abs(d[i]),r=abs(d[i]);
        while(l+5<=r)
        {
            ll lm=l+(r-l)/3,rm=r-(r-l)/3;
            if(cal(X[i],Y[i],lm)<cal(X[i],Y[i],rm))r=rm;
            else l=lm;
        }
        for(int j=l;j<=r;j++)
            pos[i]=cal(X[i],Y[i],pos[i])<cal(X[i],Y[i],j) ? pos[i]:j;
        sum+=X[i]+pos[i]*b;
    }
    sum/=b;
    for(int i=1;i<=n;i++)
    {
        if(sum<0)q.push(make_pair(cal(X[i],Y[i],pos[i]+1)-cal(X[i],Y[i],pos[i]),i));
        else q.push(make_pair(cal(X[i],Y[i],pos[i]-1)-cal(X[i],Y[i],pos[i]),i));
    }
    while(sum)
    {
        int t=q.top().second;q.pop();
        if(sum<0)
        {
            pos[t]++;sum++;
            q.push(make_pair(cal(X[t],Y[t],pos[t]+1)-cal(X[t],Y[t],pos[t]),t));
        }
        else
        {
            pos[t]--;sum--;
            q.push(make_pair(cal(X[t],Y[t],pos[t]-1)-cal(X[t],Y[t],pos[t]),t));
        }
    }
    for(int i=1;i<=n;i++)
        ans+=cal(X[i],Y[i],pos[i]);
    printf("%lld\n",ans/2);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值