BZOJ2800 [Poi2012]Leveling Ground 【扩展欧几里得 + 三分 + 堆】
题目链接
题解
区间加极难操作,差分之后可转化为两点一加一减
那么现在问题就将每个点暂时独立开来
先判定每个点是否被\((A,B)\)整除,否则无解
之后我们先将\(A,B\)化为互质,所有数除一个\((A,B)\)
求得
\[Ax + By = 1
\]
那么对于点\(d[i]\),满足
\[d[i] = A(xd[i] + kB) + B(yd[i] - kA)
\]
其中\(k\)可以取任意值
我们对于单点的目标,是最小化
\[|xd[i] + kB|+|yd[i] - kA|
\]
两个绝对值相加是一个单峰函数,利用三分法即可得出\(k\)
从而得到每个点目前最优解\(X[i] = xd[i] + kB,Y[i] = yd[i] - kA\)
但是我们做到了单个点最优,但整体不一定合法,我们必须满足正负操作次数相同
即
\[A\sum\limits_{i = 1}^{n}X[i] + B\sum\limits_{i = 1}^{n}Y[i] = 0
\]
而由于\(\sum d[i] = 0\)
故我们只需保证\(T = \sum X[i] = 0\)
显然我们只需改变\(\frac{T}{B}\)次
对于每个\(X[i]\)我们计算出它改变一次的代价,用一个堆维护即可
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
#include<cmath>
#include<map>
#define REP(i,n) for (register int i = 1; i <= (n); i++)
#define cls(s) memset(s,0,sizeof(s))
#define LL long long int
using namespace std;
const int maxn = 100005,maxm = 100005,INF = 1000000000;
inline int read(){
int out = 0,flag = 1; char c = getchar();
while (c < 48 || c > 57){if (c == '-') flag = -1; c = getchar();}
while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
return out * flag;
}
struct pr{LL v,i;};
inline bool operator <(const pr& a,const pr& b){
return a.v > b.v;
}
priority_queue<pr> q;
LL n,A,B,X,Y,d[maxn],h[maxn],xx[maxn],yy[maxn],dd;
void exgcd(LL a,LL b,LL& d,LL& x,LL& y){
if (!b){d = a; x = 1; y = 0;}
else exgcd(b,a % b,d,y,x),y -= (a / b) * x;
}
inline LL cost(int i,LL k){
return abs(X * d[i] + k * B) + abs(Y * d[i] - k * A);
}
void workmin(){
REP(i,n){
LL l = -INF,r = INF,lmid,rmid,L,R,K;
while (r - l >= 3){
lmid = (l + l + r) / 3;
rmid = (r + l + r) / 3;
L = cost(i,lmid);
R = cost(i,rmid);
if (L == R){
if (cost(i,lmid - 1) < L) r = rmid;
else l = lmid;
}
else if (L > R) l = lmid;
else r = rmid;
}
K = l;
for (int j = l + 1; j <= r; j++)
if (cost(i,j) < cost(i,K)) K = j;
xx[i] = X * d[i] + K * B;
yy[i] = Y * d[i] - K * A;
}
}
inline LL price(int i){
return abs(yy[i] - dd * A) + abs(xx[i] + dd * B) - abs(xx[i]) - abs(yy[i]);
}
void print(){
//REP(i,n) printf("(%lld,%lld)\n",xx[i],yy[i]); puts("");
LL ans = 0;
REP(i,n) ans += abs(xx[i]) + abs(yy[i]);
printf("%lld\n",ans >> 1);
}
void workok(){
LL sum = 0;
REP(i,n) sum += xx[i];
sum /= B;
dd = sum > 0 ? -1 : 1; sum = abs(sum);
REP(i,n) q.push((pr){price(i),i});
pr u;
while (sum--){
u = q.top(); q.pop();
xx[u.i] += dd * B;
yy[u.i] -= dd * A;
q.push((pr){price(u.i),u.i});
}
}
int main(){
n = read(); A = read(); B = read(); LL D;
exgcd(A,B,D,X,Y); A /= D; B /= D;
REP(i,n){
h[i] = read();
if (h[i] % D){puts("-1"); return 0;}
h[i] /= D; d[i] = h[i] - h[i - 1];
}
n++;
d[n] = -h[n - 1];
workmin();
workok();
print();
return 0;
}