zoukankan      html  css  js  c++  java
  • 扶桑号战列舰 (单调栈+线段树区间更新懒惰标记 or 栈)

    传送门

    •题目描述

    题目描述
    众所周知,一战过后,在世界列强建造超无畏级战列舰的竞争之中,旧日本海军根据“个舰优越主义”,建造了扶桑级战列舰,完工时为当时世界上武装最为强大的舰只。
    同时,扶桑号战列舰也是舰岛最为科幻的战列舰。
    当然,要建造这样的舰船,科技水平是必须的。
    同样众所周知的是,德意志科学技术天下第一,所以IJN的司令官从德国学来了一种先进的建船方法。
    一只战舰横过来可以看做一个长度为n的序列,每个位置有一个数ai表示这个位置设计的高度。这种先进的造船技术可以每次将一个区间[l,r]内的所有位置高度都+1,求到达最终设计状态的最少操作次数。
    如果你不能及时完成的话,IJN司令官会奖励你去参加苏里高海战。
    
    输入
    第一行包含一个整数n,表示序列的长度。
    第二行包含n个非负整数a1,a2,a3,…,an,表示最终的状态。
    
    输出
    输出的第一行是一个正整数m,表示最少的操作次数。
    接下来m行每行两个正整数li,ri,表示一次操作。
    你需要保证1≤li≤ri≤n。
    保证最少次数m≤105,输出可以以任意顺序输出。
    
    样例输入
    6
    2 3 3 3 3 3
    
    样例输出
    3
    1 6
    1 6
    2 6
    
    数据范围:n <= 100000;
    View Code

    •题解

      由初始状态 n 个 0 ,每次操作 +1,变为 a1,a2,...,an

      可转化为由最终状态 a1,a2,...,an,每次操作 -1,变为 n 个 0;

      由操作次数 m ≤ 105 可知 ai ≤ 105

      首先用单调栈求出以 ai 为最小值的左右区间 [li,ri];

      然后,从小到大开始枚举 ai,将 [li,ri] 区间的所有数都减 ai

      区间减数的过程用线段树维护;

      总的时间复杂度 $O(nlogn)$;

    •Code

      1 #include<bits/stdc++.h>
      2 using namespace std;
      3 #define ls(x) (x<<1)
      4 #define rs(x) (x<<1|1)
      5 const int maxn=1e5+50;
      6 
      7 int n;
      8 int a[maxn];
      9 vector<int >p[maxn];///ai ≤ 1e5,所以可以开个1e5大小的p存ai出现的位置
     10 int l[maxn];
     11 int r[maxn];
     12 stack<int >sta;
     13 struct Seg
     14 {
     15     int l,r;
     16     int lazy;
     17     int val;
     18     int mid(){return l+((r-l)>>1);}
     19 }seg[maxn<<4];
     20 struct Data
     21 {
     22     int l,r;
     23     int cnt;
     24 }ans[maxn];
     25 
     26 void buildSegTree(int l,int r,int pos)
     27 {
     28     seg[pos].l=l;
     29     seg[pos].r=r;
     30     seg[pos].lazy=0;
     31 
     32     if(l == r)
     33     {
     34         seg[pos].val=a[l];
     35         return ;
     36     }
     37     int mid=l+((r-l)>>1);
     38     buildSegTree(l,mid,ls(pos));
     39     buildSegTree(mid+1,r,rs(pos));
     40 }
     41 void pushDown(int pos)
     42 {
     43     int &lazy=seg[pos].lazy;
     44 
     45     seg[ls(pos)].lazy += lazy;
     46     seg[rs(pos)].lazy += lazy;
     47 
     48     lazy=0;
     49 }
     50 int Query(int x,int pos)
     51 {
     52     if(seg[pos].l == seg[pos].r)
     53         return seg[pos].val-seg[pos].lazy;
     54 
     55     pushDown(pos);
     56 
     57     int mid=seg[pos].mid();
     58 
     59     if(x <= mid)
     60         return Query(x,ls(pos));
     61     else
     62         return Query(x,rs(pos));
     63 }
     64 void Updata(int l,int r,int lazy,int pos)
     65 {
     66     if(seg[pos].l == l && seg[pos].r == r)
     67     {
     68         seg[pos].lazy += lazy;
     69         return ;
     70     }
     71     pushDown(pos);
     72 
     73     int mid=seg[pos].mid();
     74 
     75     if(r <= mid)
     76         Updata(l,r,lazy,ls(pos));
     77     else if(l > mid)
     78         Updata(l,r,lazy,rs(pos));
     79     else
     80     {
     81         Updata(l,mid,lazy,ls(pos));
     82         Updata(mid+1,r,lazy,rs(pos));
     83     }
     84 }
     85 void Clear()
     86 {
     87     while(!sta.empty())
     88         sta.pop();
     89 }
     90 void Work()
     91 {
     92     Clear();
     93     for(int i=1;i <= n;++i)
     94     {
     95         while(!sta.empty() && a[sta.top()] >= a[i])
     96             sta.pop();
     97 
     98         l[i]=sta.empty() ? 1:sta.top()+1;
     99         sta.push(i);
    100     }
    101     Clear();
    102     for(int i=n;i >= 1;--i)
    103     {
    104         while(!sta.empty() && a[sta.top()] >= a[i])
    105             sta.pop();
    106 
    107         r[i]=sta.empty() ? n:sta.top()-1;
    108         sta.push(i);
    109     }
    110 }
    111 void Solve()
    112 {
    113     Work();
    114     buildSegTree(1,n,1);
    115 
    116     int k=0;
    117     int x=*max_element(a+1,a+n+1);
    118     for(int i=1;i <= x;++i)///从小到大枚举ai
    119     {
    120         for(int j=0;j < p[i].size();++j)
    121         {
    122             int id=p[i][j];
    123             int cur=Query(id,1);///线段树查询当前的ai在之前的减法中还剩多少
    124 
    125             if(cur)///cur > 0
    126             {
    127                 Updata(l[id],r[id],cur,1);///l[id],r[id] 区间做cur次-1操作
    128                 ans[++k]=Data{l[id],r[id],cur};///记录
    129             }
    130         }
    131     }
    132     int m=0;
    133     for(int i=1;i <= k;++i)
    134         m += ans[i].cnt;
    135 
    136     printf("%d
    ",m);
    137     for(int i=1;i <= k;++i)
    138         for(int j=1;j <= ans[i].cnt;++j)
    139             printf("%d %d
    ",ans[i].l,ans[i].r);
    140 }
    141 int main()
    142 {
    143     scanf("%d",&n);
    144     for(int i=1;i <= n;++i)
    145     {
    146         scanf("%d",a+i);
    147         p[a[i]].push_back(i);
    148     }
    149 
    150     Solve();
    151 
    152     return 0;
    153 }
    View Code

    •简洁做法

      参考资料:中国石油大学(华东), 韩宝坤

    •我的理解

      巧妙的利用栈,用很简洁的代码实现了上述很繁琐的程序,tql;

      首先,如何求解最少的操作次数呢?

      找上升部分,如下图( x轴代表点对,y轴代表相应点对到达的高度,n = 9);

      

      在图上虚构两个点,即 0点 和 n+1 点,并使他们的高度为 0;

      你会发现对紫色部分的操作是必不可少的;

      例如图中的点 2,3 ,在点 2 下降到 0 位置后,点 3 必定还要再下降一次才可以到达 0 位置;

      所以所,上升的高度之和便是最少的操作次数,即上图紫色部分的加和;

      那么如何求解 m 次操作区间呢?

      找下降部分;

      对于点 i,如果 a[ i ] > a[ i+1 ] ,那么,通过前面必须要下降的紫色部分让 i 点下降 a[ i ]-a[ i+1 ] 次;

      使得 i 点与 i+1 点水平;

      一直执行上述操作,直到来到 n 点,通过前面的紫色部分让 n 点下降到 0 点;

      至此,执行完毕,共操作 m 次,使得全部点对都置于 0 点;

    Code

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 const int maxn=1e5+50;
     4 
     5 int n;
     6 int a[maxn];
     7 stack<int >sta;
     8 
     9 void Solve()
    10 {
    11     int m=0;
    12     for(int i=1;i <= n;++i)
    13     {
    14         if(a[i] > a[i-1])
    15             m += a[i]-a[i-1];
    16     }
    17 
    18     printf("%d
    ",m);
    19 
    20     for(int i=1;i <= n;++i)
    21     {
    22         if(a[i] > a[i-1])
    23         {
    24             for(int j=a[i-1];j < a[i];++j)
    25                 sta.push(i);
    26         }
    27         if(a[i] > a[i+1])
    28         {
    29             ///不用特判sta是否为空,因为下降前必定会有上升,上升的高度必然>=下降的高度
    30             for(int j=a[i];j > a[i+1];--j)
    31             {
    32                 printf("%d %d
    ",sta.top(),i);
    33                 sta.pop();
    34             }
    35         }
    36     }
    37 }
    38 int main()
    39 {
    40     scanf("%d",&n);
    41     for(int i=1;i <= n;++i)
    42         scanf("%d",a+i);
    43 
    44     Solve();
    45 
    46     return 0;
    47 }
    View Code
  • 相关阅读:
    刷题总结——宠物收养所(bzoj1208)
    算法复习——trie树(poj2001)
    刷题总结——bzoj2243染色
    算法复习——虚树(消耗战bzoj2286)
    设置SSH自动登陆(免密码,用户名)
    自旋锁Spin lock与互斥锁Mutex的区别
    如何去除Linux文件的^M字符
    重构
    比赛日程安排
    基于libzip的简易压缩(zip)/解压缩(unzip)程序
  • 原文地址:https://www.cnblogs.com/violet-acmer/p/11275248.html
Copyright © 2011-2022 走看看