瓜分領土(線段樹)
石頭、剪刀和布鬧別扭了,他們要分家。
他們生活在一個離散的一維空間裏,簡單點說,他們擁有在一條直線上的N間房子,每間房子有一個風水值(有正有負)。
然後,他們決定將這N間房子分成非空的三個連續段,從左到右數,第一段的房子全部屬於石頭,第二段的房子全部屬於剪刀,第三段的房子全部屬於布。
由於他們希望公平,並且又由於剪刀是他們的老大哥,他們決定根據這些條件制定了一個評判標準:
設石頭擁有的房子的風水值和為a,剪刀擁有的房子的風水值和為b,布擁有的房子的風水值和為c,剪刀擁有n間房子。
那麽通過給定一個參數x。
那麽,這種分配的合理值就是max(a,b,c)-min(a,b,c)+x*n.
合理值越小,表示這種分配越合理。
因此,我們現在就是要求出這個最小的合理值。
對於30%的數據,N<=10.
對於70%的數據,N<=1000.
對於100%的數據,N<=100000,保證所有運算結果在long long範圍內。
輸入格式
第一行一個正整數N。
第二行有N個整數,表示房子的風水值,按從左到右的順序給出。
第三行一個整數x。
輸出格式
一行一個整數,表示最小的合理值。
輸入樣例
41 1 1 1
-1
輸出樣例
-1
題解:
看到有max和min,感覺有點煩,不如我們強行把順序定下,就會出現6種情況,這裏只選a>b>c來說。我們規定i為石頭房屋的結尾,j為剪刀房屋的結尾。
當a>b>c時,即sum[i]>sum[j]-sum[i]>sum[n]-sum[j],我們把這個式子整理可得:
2*sum[i]>sum[j] sum[i]<2*sum[j]-sum[n] i<j
所以當我們按前綴和sum排好序後,枚舉j的時候,屬於該情況下可行的i一定是連續的一段,於是我們可以二分找出i的範圍。
當a>b>c時,合理值即為:sum[i]-(sum[n]-sum[j])+x*(j-i) = sum[j]+xj-sum[n]-xi+sum[i]-xi;
因為我們枚舉的是j,對於每一個j,sum[j]+xj-sum[n]是固定的,我們要求合理的i中sum[i]-xi的最小值,又因為合理的j按sum[j]排序後是連續一段的,所以我們考慮用線段樹維護。
當a>b>c時,我們開一棵線段樹,以sum[i]為關鍵字,插入sum[i]-xi,並且維護最小值。
每當我們枚舉一個j時,把j-1的信息相應的存入線段樹中。別忘了還有其他五種情況,再次不一一列舉。
所以我們枚舉j要n次,插入j-1也是n次,然後二分要logn,查詢要logn,總的復雜度大概O(nlogn*6)。不虛!
#include<algorithm> #include<fstream> #include<iostream> #include<cmath> #include<cstdio> #include<cstring> #include<cstdlib> using namespace std; int n,ds[100010],lg; long long x,p[100010]; struct tedge { long long he; int x; }sum[100010]; long long tree[800010][7],ans; bool cmp(tedge a,tedge b) { return a.he<b.he; } void Updata(int zu,int root,int l,int r,int x,long long shu) { if (l==r&&r==x) { tree[root][zu] = shu; return; } int mid = (l+r)/2; if (x<=mid) Updata(zu,root*2,l,mid,x,shu); else Updata(zu,root*2+1,mid+1,r,x,shu); tree[root][zu] = min(tree[root*2][zu],tree[root*2+1][zu]); return; } long long Query(int zu,int root,int l,int r,int cl,int cr) { if (cl<=l&&r<=cr) return tree[root][zu]; if (cl>r||cr<l) return 1e18; int mid=(l+r)/2; return min(Query(zu,root*2,l,mid,cl,cr),Query(zu,root*2+1,mid+1,r,cl,cr)); } void twofen(long long h,long long t,int zu,int i) { int l=0,r=n+1,ph,pt; while (l+1<r) { int mid = (l+r)/2; if (sum[mid].he>=h) r = mid; else l = mid; } ph = r; l=0; r=n+1; while (l+1<r) { int mid = (l+r)/2; if (sum[mid].he<=t) l = mid; else r = mid; } pt = l; if (ph>pt) return; long long counter; if (zu==1) counter = sum[ds[n]].he-sum[ds[i]].he-x*i+Query(zu,1,1,n,ph,pt); else if (zu==2) counter = Query(zu,1,1,n,ph,pt)-2*sum[ds[i]].he-x*i; else if (zu==3) counter = Query(zu,1,1,n,ph,pt)+2*sum[ds[i]].he-x*i; else if (zu==4) counter = Query(zu,1,1,n,ph,pt)+sum[ds[n]].he+sum[ds[i]].he-x*i; else if (zu==5) counter = Query(zu,1,1,n,ph,pt)-sum[ds[n]].he-sum[ds[i]].he-x*i; else if (zu==6) counter = Query(zu,1,1,n,ph,pt)-sum[ds[n]].he+sum[ds[i]].he-x*i; ans = min(ans,counter); } void erfen(int x,int i) { long long h,t; if (x==1) {h=2*sum[ds[i]].he; t=(sum[ds[n]].he+sum[ds[i]].he)/2; } else if (x==2) {h=(sum[ds[n]].he+sum[ds[i]].he+1)/2; t=sum[ds[n]].he-sum[ds[i]].he; } else if (x==3) {h=sum[ds[n]].he-sum[ds[i]].he; t=(sum[ds[n]].he+sum[ds[i]].he)/2; } else if (x==4) {t=min(2*sum[ds[i]].he,sum[ds[n]].he-sum[ds[i]].he); h=-1e17;} else if (x==5) {h=max(2*sum[ds[i]].he,sum[ds[n]].he-sum[ds[i]].he); t=1e18;} else if (x==6) {h=(sum[ds[n]].he+sum[ds[i]].he+1)/2; t=2*sum[ds[i]].he;} twofen(h,t,x,i); } int main() { freopen("2070.in","r",stdin); freopen("2070.out","w",stdout); scanf("%d",&n); for (int i=1; i<=n; i++) scanf("%lld",&p[i]); scanf("%lld",&x); for (int i=1; i<=n; i++) sum[i].he = sum[i-1].he+p[i]; for (int i=1; i<=n; i++) sum[i].x = i; sort(sum+1,sum+1+n,cmp); for (int i=1; i<=n; i++) ds[sum[i].x] = i; lg = 1; while (lg<n) lg = lg*2; for (int i=1; i<=lg*2; i++) for (int j=1; j<=6; j++) tree[i][j] = 1e17; ans = 1e17; for (int i=n-2; i>=1; i--) { int j = i+1; Updata(1,1,1,n,ds[j],x*j-sum[ds[j]].he);//abc Updata(2,1,1,n,ds[j],sum[ds[j]].he+x*j);//acb Updata(3,1,1,n,ds[j],x*j-sum[ds[j]].he);//bca Updata(4,1,1,n,ds[j],x*j-2*sum[ds[j]].he);//bac Updata(5,1,1,n,ds[j],x*j+2*sum[ds[j]].he);//cab Updata(6,1,1,n,ds[j],sum[ds[j]].he+x*j);//cba for (int k=1; k<=6; k++) erfen(k,i); } printf("%lld\n",ans); return 0; }
不過這道題的推理這的很煩人,寫起來也有點煩!
瓜分領土(線段樹)