zoukankan      html  css  js  c++  java
  • 题解报告—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
    ");
    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 }
  • 相关阅读:
    图片展示和上传需要注意的问题
    大数据技能学习
    C#100万条数据导入SQL SERVER数据库仅用4秒 (附源码)
    领导力
    .NetCore 三种生命周期注入方式
    Redis常见面试题
    .NET Core开发日志——Middleware
    编程的灵魂
    递推算法
    分治算法
  • 原文地址:https://www.cnblogs.com/genius777/p/8619170.html
Copyright © 2011-2022 走看看