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|最小,可以采取三分.(也可以用改良的二分(-_-??),跑得快一丢丢,虽然代码很诡异)

  但现在问题又来了,求出了每个数字的xiyi保证了答案的最优性,但有可能不保证合法性,即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这套题里质量很高的题之一!!!
受益匪浅.

展开阅读全文

没有更多推荐了,返回首页