BZOJ2800/POI2012 Leveling Ground

Task
给出n个整数X_1,X_2,…X_n,再给出两个正整数a、b,可以进行下面四种操作:
1. 选择正整数l,r (1<=l<=r<=n),将X_l,X_{l+1},…,X_r都加上a。
2. 选择正整数l,r (1<=l<=r<=n),将X_l,X_{l+1},…,X_r都减去a。
3. 选择正整数l,r (1<=l<=r<=n),将X_l,X_{l+1},…,X_r都加上b。
4. 选择正整数l,r (1<=l<=r<=n),将X_l,X_{l+1},…,X_r都减去b。
求最少的操作次数将{X_i}全部变成0.
n<=100,000, a,b<=10^9, |X_i|<=10^9

Solution
题目有个非常清(讨)奇(厌)的条件:每次操作的区间是不确定的,这样就不能把每个数字单独考虑,如果把问题转化成求每个数字i变为 xi 的最小代价,那就容易很多.
那么我们就考虑是否能进行这样的转化.

区间[l,r]都+a转为单点操作->
1.对区间内每个数字分别进行操作.
2.差分!!对第l个数字+a,第r+1个数字-a,这样需要保证对所有i,前i个数字的和为0.
第二种转化显然更优,而且也是可行的,确定了前i个数字的和以及每个数字的初值,就可以求出每个数字需要改变的总量 di .

  现在的问题转化为对每个i,求最小的 |x|+|y| 使得 xa+yb=di .
对于这个问题可以通过扩展欧几里得求解,求出 xa+yb=1 的任意一组解 x0,y0 ,那么对于 i ,有
  xi=x0di+Tb,yi=y0diTa.
  为了使得 |xi|+|yi| 最小,可以采取三分.(也可以用改良的二分(-_-??),跑得快一丢丢,虽然代码很诡异)

  但现在问题又来了,求出了每个数字的 xi yi 保证了答案的最优性,但有可能不保证合法性,即 xi=yi=0 (每次对左端点 l 进行+a,对 r+1 进行 a ,确定最后的操作系数和为0).那么就要对当前的 xi , yi 进行修改.

  首先确定,当 xi=0 时, yi=0 ,因为 xia+yib=0 ,那么只要考虑对 xi 进行修改.

  对某个 xi , 根据 xi=x0di+Tb ,修改一次至少使它减少(或增加)b,设这样的修改为单位修改,假设当前 xi=t>0 ,一共就要进行 t/b 次单位修改.每次单位修改的对象是任意的,每个数字被修改的次数也是无限制的,而我们肯定希望修改后对答案的影响最小.
  
  只要我们把每个数字进行一次单位修改对答案的影响作为比较的标准,用堆来维护当前对答案影响最小的数字i,每次得到堆顶元素后,对xi进行一次单位修改,重新计算它再进行一次单位修改对答案的影响,再丢入堆里,重复 t/b 次就可以得到最优答案啦.
  
注意:
1. di 不是 gcd(a,b) 的倍数时无解.
2.三分的范围不能过大,否则爆long long.

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#define ll long long
using namespace std;
const int M=1e5+5;
int n;
struct node{
    int id;
    ll v;
    bool operator<(const node &tmp)const {
        return v>tmp.v;
    }
};
priority_queue<node>Q;
ll A,B,C,s[M],fx[M],fy[M];
ll gcd(ll a,ll b){
    if(!b)return a;
    return gcd(b,a%b);
}
ll ex_gcd(ll a,ll b,ll &x,ll &y){
    if(b==0){
        x=1;y=0;
        return a;
    }
    ll res=ex_gcd(b,a%b,y,x);// ax+by= ay'+b(x'-a/b*y')
    y-=a/b*x;
    return res;
}
ll val(int id,ll k){
    return abs(fx[id]-k*B)+abs(fy[id]+k*A);
}
void chk(int id,ll v){//求k,使得 |fx[id]-k*B|+|fy[id]+k*A| 最小 
    ll ans=val(id,0);
    ll l=-abs(s[id]-s[id-1]),r=abs(s[id]-s[id-1]),k=0;
    while(l<=r){
        ll i,mid=(l+r)>>1,f=0;
        ll a=val(id,mid);//b=val(id,mid);
        if(a<ans){k=mid;ans=a;}
        if(l==r)break;
        for(i=mid+1;i<=r;i++){
            ll b=val(id,i);
            if(a>b){
                l=i;f=1;break;
            }
            else if(a<b){
                r=mid-1;f=1;break;
            }
        }
        if(!f)break;
    }
    fx[id]-=k*B;
    fy[id]+=k*A;
    return;
}
ll cost(int i){
    return abs(fx[i]-B)+abs(fy[i]+A)-abs(fx[i])-abs(fy[i]);
}
ll solve(){
    int i,j,k;
    ll c,d,cnt=0,b=0;//1为正 
    C=gcd(A,B);
    if(A==B){//特判 
        for(i=1;i<=n+1;i++){
            ll t=s[i]-s[i-1];
            if(t%A!=0)return -1;
            cnt+=abs(t/A);
        }
        return cnt/2;
    }
    A/=C;B/=C;
    ex_gcd(A,B,fx[0],fy[0]);
    for(i=1;i<=n+1;i++){;
        ll t=s[i]-s[i-1];
        if(t%C!=0)return -1;
        t/=C; 
        fx[i]=fx[0]*t,fy[i]=fy[0]*t;
        chk(i,t);//找到最小的|fx[i]|+|fy[i]| 
        cnt+=fx[i];
        b+=fy[i];
    }
    if(cnt<0){//转化a,b,xi,yi 
        for(i=0;i<=n+1;i++)swap(fx[i],fy[i]);
        swap(A,B);
        cnt=b;
    }
    cnt/=B;//默认减小fx[i]
    for(i=1;i<=n+1;i++)Q.push((node){i,cost(i)});//丢入调整数字i后的影响 
    while(cnt--){
        int id=Q.top().id;Q.pop();
        fx[id]-=B;fy[id]+=A;
        Q.push((node){id,cost(id)});
    }
    cnt=0;
    for(i=1;i<=n+1;i++)cnt+=abs(fx[i])+abs(fy[i]);
    return cnt/2;
}
int main(){
    memset(s,0,sizeof(s));
    scanf("%d %d %d",&n,&A,&B);
    for(int i=1;i<=n;i++)cin>>s[i];
    cout<<solve()<<endl;
    return 0;
}

感觉这是POI2012这套题里质量很高的题之一!!!
受益匪浅.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值