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變爲
那麼我們就考慮是否能進行這樣的轉化.
區間[l,r]都+a轉爲單點操作->
1.對區間內每個數字分別進行操作.
2.差分!!對第l個數字+a,第r+1個數字-a,這樣需要保證對所有i,前i個數字的和爲0.
第二種轉化顯然更優,而且也是可行的,確定了前i個數字的和以及每個數字的初值,就可以求出每個數字需要改變的總量
現在的問題轉化爲對每個i,求最小的
對於這個問題可以通過擴展歐幾里得求解,求出
爲了使得
但現在問題又來了,求出了每個數字的
首先確定,當
對某個
只要我們把每個數字進行一次單位修改對答案的影響作爲比較的標準,用堆來維護當前對答案影響最小的數字i,每次得到堆頂元素後,對xi進行一次單位修改,重新計算它再進行一次單位修改對答案的影響,再丟入堆裏,重複
注意:
1.
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這套題裏質量很高的題之一!!!
受益匪淺.