瓜分领土(线段树)
石头、剪刀和布闹别扭了,他们要分家。
他们生活在一个离散的一维空间里,简单点说,他们拥有在一条直线上的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。
输出格式
一行一个整数,表示最小的合理值。
输入样例
1 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; }
不过这道题的推理这的很烦人,写起来也有点烦!