[BZOJ4293]Siano
【description】
- 农夫Byteasar买了一片n亩的土地,他要在这上面种草。
他在每一亩土地上都种植了一种独一无二的草,其中,第i亩土地的草每天会长高a[i]厘米。
Byteasar一共会进行m次收割,其中第i次收割在第d[i]天,并把所有高度大于等于b[i]的部分全部割去。Byteasar想知道,每次收割得到的草的高度总和是多少,你能帮帮他吗?
【Input】
- 第一行包含两个正整数n,m(1<=n,m<=500000),分别表示亩数和收割次数。
- 第二行包含n个正整数,其中第i个数为ai,依次表示每亩种植的草的生长能力。
- 接下来m行,每行包含两个正整数d[i],bi,依次描述每次收割。数据保证d[1]< d[2]< …<d[m],并且任何时刻没有任何一亩草的高度超过10^12。
【Output】
- 输出m行,每行一个整数,依次回答每次收割能得到的草的高度总和。
【Sample Input】
4 4
1 2 4 3
1 1
2 2
3 0
4 4
【Sample Output】
6
6
18
0
【Data Constraints】
- 见【Input】
【Hint or 样例说明】
- 第1天,草的高度分别为1,2,4,3,收割后变为1,1,1,1。
- 第2天,草的高度分别为2,3,5,4,收割后变为2,2,2,2。
- 第3天,草的高度分别为3,4,6,5,收割后变为0,0,0,0。
- 第4天,草的高度分别为1,2,4,3,收割后变为1,2,4,3。
【考场TIME】
这道题是在一次春季体验营的DAY1 T4。当时一看到这道题就知道要用线段树写,然而当时并没有太多时间来整理一下这道题的思路,就随便打了一个很丑的线段树,就连植物生长的修改都是O(n)修改,那很显然就GG了,果然考完发现自己写的线段树和暴力一个分,然后就。。。脸上笑嘻嘻,心里MMP。
考完才发现这道题居然是BZOJ上的原题。。。这也太水了吧。。。(还以为原创,结果换个题目直接上了)
【个人分析】
考完之后就和几个同学一起讨论了一下,大概知道了思路,其实这道题有几个关键点:
1、生长速度快的植物的高度永远不会比生长速度慢的植物的高度低,至少都相等。
这是一个很关键的点,对于这个性质我们可以对按生长速率对植物排序,然后就可以使其单调,接着就可以二分查找每次从哪个位置开始切割了,听起来就很优秀的样子。
2、线段树需要特别记录的东西
cc——>懒标记,记录当前区间被剪成了多高。
add——>生长时间,记录从上一次询问到这一次的时间,但注意在有cc的情况下时就要清零,因为切割操作会使生长无效,且我们每次都是先生长再切割。
sum——>区间植物总高度
maxn——>区间植物最高高度
4、记录生长速度的前缀和
这也是一个关键,如果不记录生长速度前缀和就没办法用线段树来合并maxn,sum。
3、我们每次只需要记录切割之前的高度剩余高度
我们可以一边剪的同时一边查询,就很舒服。
【代码实现】
1 #include<cstdio> 2 #include<vector> 3 #include<algorithm> 4 using namespace std; 5 struct sd{ 6 int l,r,son[2]; 7 long long maxn,sum,add,cc; 8 }t[1000005]; 9 long long v[500005];//生长速度 10 long long prev[500005];//生长速度前缀和 11 int cnt; 12 void build(int &k,int l1,int r1) 13 { 14 cnt++;k=cnt; 15 t[k].l=l1,t[k].r=r1,t[k].cc=-1; 16 if(l1==r1) 17 { 18 t[k].maxn=0;t[k].sum=0; 19 return; 20 } 21 int mid=(l1+r1)>>1; 22 build(t[k].son[0],l1,mid); 23 build(t[k].son[1],mid+1,r1); 24 } 25 void update(int k,long long tt)//更新 26 { 27 t[k].add+=tt;//生长每次只需要将生长的add标记丢到根节点即可 28 t[k].sum+=(prev[t[k].r]-prev[t[k].l-1])*tt; 29 t[k].maxn+=tt*v[t[k].r]; 30 } 31 void pushdown(int k) 32 { 33 int ls=t[k].son[0]; 34 int rs=t[k].son[1]; 35 if(t[k].cc!=-1) 36 {//在有cc的情况下时就要清零,因为切割操作会使生长无效,且我们每次都是先生长再切割。 37 t[ls].cc=t[rs].cc=t[k].cc; 38 t[ls].add=t[rs].add=0; 39 t[ls].maxn=t[rs].maxn=t[k].cc; 40 t[ls].sum=(t[ls].r-t[ls].l+1)*t[k].cc; 41 t[rs].sum=(t[rs].r-t[rs].l+1)*t[k].cc; 42 t[k].cc=-1; 43 } 44 if(t[k].add)//每次更新当前节点的子树 45 { 46 update(ls,t[k].add); 47 update(rs,t[k].add); 48 t[k].add=0; 49 } 50 } 51 int ask(int k,long long hh)//二分查询从哪里开始切割 52 { 53 if(t[k].l==t[k].r) 54 return t[k].l; 55 pushdown(k); 56 int ls=t[k].son[0],rs=t[k].son[1]; 57 if(t[ls].maxn>=hh) return ask(ls,hh); 58 else return ask(rs,hh); 59 } 60 void pushup(int k) 61 { 62 int ls=t[k].son[0],rs=t[k].son[1]; 63 t[k].maxn=t[rs].maxn; 64 t[k].sum=t[ls].sum+t[rs].sum; 65 } 66 long long cut(int k,int pos,long long hh)//切割顺带查询 67 { 68 long long res=0; 69 if(t[k].r<pos) return 0; 70 if(t[k].l>=pos) 71 { 72 res=t[k].sum;//统计切割前的高度 73 t[k].sum=(t[k].r-t[k].l+1)*hh; 74 t[k].maxn=hh=t[k].cc=hh; 75 t[k].add=0; 76 return res; 77 } 78 pushdown(k); 79 res=cut(t[k].son[0],pos,hh)+cut(t[k].son[1],pos,hh); 80 pushup(k); 81 return res; 82 } 83 int main() 84 { 85 int n,m; 86 scanf("%d%d",&n,&m); 87 for(int i=1;i<=n;i++) 88 scanf("%lld",&v[i]); 89 sort(v+1,v+n+1); 90 for(int i=1;i<=n;i++) 91 prev[i]=prev[i-1]+v[i]; 92 int root; 93 build(root,1,n); 94 long long last=0;//记录上一次的操作时间便于记录生长时间 95 for(int i=1;i<=m;i++) 96 { 97 long long tt,hh; 98 scanf("%lld%lld",&tt,&hh); 99 update(1,tt-last);last=tt; 100 if(t[1].maxn<=hh) 101 { 102 printf("0 "); 103 continue; 104 } 105 int gg=ask(1,hh); 106 printf("%lld ",cut(1,gg,hh)-(n-gg+1)*hh); 107 //只需要查询切割区间的修改值 108 } 109 return 0; 110 }