扶桑号战列舰 (单调栈+线段树区间更新懒惰标记 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
原文地址:https://www.cnblogs.com/violet-acmer/p/11275248.html