瓜分领土(线段树)

石头、剪刀和布闹别扭了,他们要分家。
他们生活在一个离散的一维空间里,简单点说,他们拥有在一条直线上的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。

输出格式

一行一个整数,表示最小的合理值。

输入样例

4
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;
}

不过这道题的推理这的很烦人,写起来也有点烦!

时间: 2024-11-25 03:49:10

瓜分领土(线段树)的相关文章

[poj2104]可持久化线段树入门题(主席树)

解题关键:离线求区间第k小,主席树的经典裸题: 对主席树的理解:主席树维护的是一段序列中某个数字出现的次数,所以需要预先离散化,最好使用vector的erase和unique函数,很方便:如果求整段序列的第k小,我们会想到离散化二分和线段树的做法, 而主席树只是保存了序列的前缀和,排序之后,对序列的前缀分别做线段树,具有差分的性质,因此可以求任意区间的第k小,如果主席树维护索引,只需要求出某个数字在主席树中的位置,即为sort之后v中的索引:若要求第k大,建树时反向排序即可 1 #include

【BZOJ4942】[Noi2017]整数 线段树+DFS(卡过)

[BZOJ4942][Noi2017]整数 题目描述去uoj 题解:如果只有加法,那么直接暴力即可...(因为1的数量最多nlogn个) 先考虑加法,比较显然的做法就是将A二进制分解成log位,然后依次更新这log位,如果最高位依然有进位,那么找到最高位后面的第一个0,将中间的所有1变成0,那个0变成1.这个显然要用到线段树,但是复杂度是nlog2n的,肯定过不去. 于是我在考场上yy了一下,这log位是连续的,我们每次都要花费log的时间去修改一个岂不是很浪费?我们可以先在线段树上找到这段区间

bzoj1798: [Ahoi2009]Seq 维护序列seq 线段树

题目传送门 这道题就是线段树 先传乘法标记再传加法 #include<cstdio> #include<cstring> #include<algorithm> #define LL long long using namespace std; const int M=400010; LL read(){ LL ans=0,f=1,c=getchar(); while(c<'0'||c>'9'){if(c=='-') f=-1; c=getchar();}

Vijos P1066 弱弱的战壕【多解,线段树,暴力,树状数组】

弱弱的战壕 描述 永恒和mx正在玩一个即时战略游戏,名字嘛~~~~~~恕本人记性不好,忘了-_-b. mx在他的基地附近建立了n个战壕,每个战壕都是一个独立的作战单位,射程可以达到无限(“mx不赢定了?!?”永恒[email protected][email protected]). 但是,战壕有一个弱点,就是只能攻击它的左下方,说白了就是横纵坐标都不大于它的点(mx:“我的战壕为什么这么菜”ToT).这样,永恒就可以从别的地方进攻摧毁战壕,从而消灭mx的部队. 战壕都有一个保护范围,同它的攻击

luogu 1712 区间(线段树+尺取法)

题意:给出n个区间,求选择一些区间,使得一个点被覆盖的次数超过m次,最小的花费.花费指的是选择的区间中最大长度减去最小长度. 坐标值这么大,n比较小,显然需要离散化,需要一个技巧,把区间转化为半开半闭区间,然后线段树的每一个节点表示一个半开半闭区间. 接着我们注意到需要求最小的花费,且这个花费只与选择的区间集合中的最大长度和最小长度有关. 这意味着如果最大长度和最小长度一定,我们显然是需要把中间长度的区间尽量的选择进去使答案不会变的更劣. 不妨把区间按长度排序,枚举每个最小长度区间,然后最大区间

【BZOJ】1382: [Baltic2001]Mars Maps (线段树+扫描线)

1382: [Baltic2001]Mars Maps Time Limit: 5 Sec  Memory Limit: 64 MB Description 给出N个矩形,N<=10000.其坐标不超过10^9.求其面积并 Input 先给出一个数字N,代表有N个矩形. 接下来N行,每行四个数,代表矩形的坐标. Output 输出面积并 Sample Input 2 10 10 20 20 15 15 25 30 Sample Output 225 本以为是傻逼题,没想到不容易啊- 线段树+扫描

BZOJ 1012: [JSOI2008]最大数maxnumber(线段树)

012: [JSOI2008]最大数maxnumber Time Limit: 3 Sec  Memory Limit: 162 MB Description 现在请求你维护一个数列,要求提供以下两种操作:1. 查询操作.语法:Q L 功能:查询当前数列中末尾L个数中的最大的数,并输出这个数的值.限制:L不超过当前数列的长度.2. 插入操作.语法:A n 功能:将n加上t,其中t是最近一次查询操作的答案(如果还未执行过查询操作,则t=0),并将所得结果对一个固定的常数D取模,将所得答案插入到数列

HDU 1754 I Hate It(线段树之单点更新,区间最值)

I Hate It Time Limit: 9000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 70863    Accepted Submission(s): 27424 Problem Description 很多学校流行一种比较的习惯.老师们很喜欢询问,从某某到某某当中,分数最高的是多少. 这让很多学生很反感.不管你喜不喜欢,现在需要你做的是,就是按照老师的

线段树入门总结

线段树的入门级 总结   线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点.      对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b].因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度.      使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN).而未优化的空间复杂度为2N,因此有时需要离散化让空间