扶桑号战列舰 (单调栈+线段树区间更新懒惰标记 or 栈)
•题目描述
View Code题目描述 众所周知,一战过后,在世界列强建造超无畏级战列舰的竞争之中,旧日本海军根据“个舰优越主义”,建造了扶桑级战列舰,完工时为当时世界上武装最为强大的舰只。 同时,扶桑号战列舰也是舰岛最为科幻的战列舰。 当然,要建造这样的舰船,科技水平是必须的。 同样众所周知的是,德意志科学技术天下第一,所以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;
•题解
由初始状态 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
View Code1 #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\n",m); 137 for(int i=1;i <= k;++i) 138 for(int j=1;j <= ans[i].cnt;++j) 139 printf("%d %d\n",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 }
•简洁做法
参考资料:
在图上虚构两个点,即 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
View Code1 #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\n",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\n",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 }