题解报告—Sjano

[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】

  1. 4 4
  2. 1 2 4 3
  3. 1 1
  4. 2 2
  5. 3 0
  6. 4 4

【Sample Output】

  1. 6
  2. 6
  3. 18
  4. 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\n");
103             continue;
104         }
105         int gg=ask(1,hh);
106         printf("%lld\n",cut(1,gg,hh)-(n-gg+1)*hh);
107         //只需要查询切割区间的修改值 
108     }
109     return 0;
110 }

 

posted @ 2018-03-21 19:49  genius777  阅读(236)  评论(0编辑  收藏  举报