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這套題裏質量很高的題之一!!!
受益匪淺.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章