[关闭]
@RabbitHu 2017-08-27T09:28:12.000000Z 字数 5278 阅读 1829

单调栈、单调队列专题

笔记


51nod 1158 全是1的最大子矩阵 | 单调栈

给出1个M*N的矩阵M1,里面的元素只有0或1,找出M1的一个子矩阵M2,M2中的元素只有1,并且M2的面积是最大的。输出M2的面积。
Input
第1行:2个数m,n中间用空格分隔(2 <= m,n <= 500)
第2 - N + 1行:每行m个数,中间用空格分隔,均为0或1。
Output
输出最大全是1的子矩阵的面积。

枚举子矩阵的最下面一行,对每一位求向上的前缀和,枚举前缀和最小位,向左右延伸得到最大的以它为最小值的区间,区间宽度*这个最小值来更新答案。

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <cmath>
  4. #include <algorithm>
  5. #include <iostream>
  6. using namespace std;
  7. const int N = 502;
  8. int m, n, mp[N][N], sum[N], stk[N], top, toleft[N], toright[N], ans;
  9. int main(){
  10. scanf("%d%d", &m, &n);
  11. for(int i = 1; i <= n; i++)
  12. for(int j = 1; j <= m; j++)
  13. scanf("%d", &mp[i][j]);
  14. for(int i = 1; i <= n; i++){
  15. for(int j = 1; j <= m; j++)
  16. sum[j] = mp[i][j] ? sum[j] + 1 : 0;
  17. top = 0, stk[0] = 0;
  18. for(int j = 1; j <= m; j++){
  19. while(top && sum[stk[top]] >= sum[j]) top--;
  20. toleft[j] = stk[top];
  21. stk[++top] = j;
  22. }
  23. top = 0, stk[0] = m + 1;
  24. for(int j = m; j; j--){
  25. while(top && sum[stk[top]] >= sum[j]) top--;
  26. toright[j] = stk[top];
  27. stk[++top] = j;
  28. }
  29. for(int j = 1; j <= m; j++)
  30. ans = max(ans, sum[j] * (toright[j] - toleft[j] - 1));
  31. }
  32. printf("%d\n", ans);
  33. return 0;
  34. }

51nod 1255 字典序最小的序列 | 贪心 单调栈

给出一个由a-z组成的字符串S,求他的一个子序列,满足如下条件:

1、包含字符串中所有出现过的字符各1个。
2、是所有满足条件1的串中,字典序最小的。

例如:babbdcc,出现过的字符为:abcd,而包含abcd的所有子序列中,字典序最小的为abdc。
Input
输入1行字符串S,所有字符均为小写,字符串的长度为L。(1 <= L <= 100000)。
Output
输出包含S中所有出现过的字符,每个字符各1个,并且字典序最小的S的子序列。

维护一个栈,从左往右,如果栈顶元素比当前元素大,且以后还会出现,则弹出。

注意栈中元素不能重复,且根本不打算放进去的元素是不能用来把别的元素弹出去的。

  1. #include <cstdio>
  2. #include <cmath>
  3. #include <cstring>
  4. #include <algorithm>
  5. #include <iostream>
  6. using namespace std;
  7. const int N = 100005;
  8. char s[N], stk[28];
  9. int n, last[128], top;
  10. bool vis[128];
  11. int main(){
  12. scanf("%s", s + 1);
  13. n = strlen(s + 1);
  14. for(int i = n; i; i--)
  15. if(!last[s[i]]) last[s[i]] = i;
  16. for(int i = 1; i <= n; i++){
  17. if(vis[s[i]]) continue;
  18. while(top && stk[top] > s[i] && last[stk[top]] > i)
  19. vis[stk[top--]] = 0;
  20. vis[stk[++top] = s[i]] = 1;
  21. }
  22. for(int i = 1; i <= top; i++)
  23. printf("%c", stk[i]);
  24. puts("");
  25. return 0;
  26. }

51nod 1423 最大二“货” | 单调栈

白克喜欢找一个序列中的次大值。对于一个所有数字都不同的序列 ,他的次大值是最大的 xj ,并且满足
对于一个所有数字都不同的序列 ,他的幸运数字是最大值和次大值的异或值(Xor)。
现在有一个序列 表示子段 。你的任务是找出所有子段的最大幸运数字。
注意,序列s中的所有数字都是不同的。

从左到右扫一遍,维护单调递减栈,要把一个新元素放进去的时候,它在栈中的前一个元素可以作为区间最大值,新元素作为区间次大值;但是从左到右扫一遍只能求最大值在左、次大值在右的情况,从右到左再扫一遍,就能求出所有情况了。

  1. #include <cstdio>
  2. #include <cmath>
  3. #include <cstring>
  4. #include <algorithm>
  5. #include <iostream>
  6. using namespace std;
  7. typedef long long ll;
  8. int read(){
  9. char c;
  10. while((c = getchar()) < '0' || c > '9');
  11. int ret = c - '0';
  12. while((c = getchar()) >= '0' && c <= '9')
  13. ret = ret * 10 + c - '0';
  14. return ret;
  15. }
  16. const int N = 100005;
  17. int n, a[N], stk[N], top, ans;
  18. int main(){
  19. while(~scanf("%d", &n)){
  20. for(int i = 1; i <= n; i++)
  21. a[i] = read();
  22. top = 0;
  23. for(int i = 1; i <= n; i++){
  24. while(top && stk[top] < a[i]) top--;
  25. ans = max(ans, stk[top] ^ a[i]);
  26. stk[++top] = a[i];
  27. }
  28. top = 0;
  29. for(int i = n; i; i--){
  30. while(top && stk[top] < a[i]) top--;
  31. ans = max(ans, stk[top] ^ a[i]);
  32. stk[++top] = a[i];
  33. }
  34. printf("%d\n", ans);
  35. }
  36. return 0;
  37. }

51nod 1275 连续子段的差异 | 单调队列

给出一个包括N个元素的整数数组A,包括A本身在内,共有 (N+1)*N / 2个非空子段。例如:1 3 2的子段为{1} {3} {2} {1 3} {3 2} {1 3 2}。在这些子段中,如果最大值同最小值的差异不超过K,则认为这是一个合格的子段。给出数组A和K,求有多少符合条件的子段。例如:3 5 7 6 3,K = 2,符合条件的子段包括:{3} {5} {7} {6} {3} {3 5} {5 7} {7 6} {5 7 6},共9个。
Input
第1行:2个数N, K(1 <= N <= 50000, 0 <= K <= 10^9)
第2 - N + 1行:每行1个数,对应数组的元素Ai(0 <= A[i] <= 10^9)
Output
输出符合条件的子段数量。

扫一遍,把每个元素丢进一个能求最大值最小值的队列里,如果最大值减最小值大于K了就从队列左边弹出元素。

  1. #include <cstdio>
  2. #include <cmath>
  3. #include <cstring>
  4. #include <algorithm>
  5. #include <iostream>
  6. using namespace std;
  7. const int N = 50005;
  8. int n, k, ans, a[N], qmin[N], qmax[N], l, r, lmin, rmin, lmax, rmax;
  9. int main(){
  10. scanf("%d%d", &n, &k);
  11. for(int i = 1; i <= n; i++)
  12. scanf("%d", &a[i]);
  13. lmin = lmax = l = 1, rmin = rmax = 0;
  14. for(r = 1; r <= n; r++){
  15. while(lmin <= rmin && a[qmin[rmin]] >= a[r]) rmin--;
  16. qmin[++rmin] = r;
  17. while(lmax <= rmax && a[qmax[rmax]] <= a[r]) rmax--;
  18. qmax[++rmax] = r;
  19. while(a[qmax[lmax]] - a[qmin[lmin]] > k && lmin <= rmin && lmax <= rmax){
  20. if(qmin[lmin] == l) lmin++;
  21. if(qmax[lmax] == l) lmax++;
  22. l++;
  23. }
  24. ans += r - l + 1;
  25. }
  26. printf("%d\n", ans);
  27. return 0;
  28. }

51nod 1086 多重背包 O(VN) 做法 | 单调队列

种物品,每种物品的数量为。从中任选若干件放在容量为的背包里,每种物品的体积为(为整数),与之相对应的价值为(为整数)。求背包能够容纳的最大价值。

Input
第1行,2个整数,中间用空格隔开。为物品的种类,为背包的容量。(1 <= <= 100,1 <= <= 50000)
第2 - + 1行,每行3个整数, 分别是物品体积、价值和数量。(1 <= <= 10000, 1 <= <= 200)

Output
输出可以容纳的最大价值。

多重背包。

首先写出朴素的状态转移方程(表示前个物品、总体积为):

直接这样算的复杂度最差可达极大、极小时)。

那么我们如何优化呢?考虑时的计算过程:



提出一些,将以上三式整理一下:



可以发现,以上三式的计算中有许多重复的地方。

那么如何消除重复,达到优化呢?观察原始方程:

,则上式等价于:

那么在每个i循环中,可以把所有的j按a分成w[i]组,每组新建一个队列,枚举b,每次把放进队列,并保持队列长度不超过c[i],用O(1)求出队列中最大值、从最大值转移即可。

  1. #include <cstdio>
  2. #include <cmath>
  3. #include <cstring>
  4. #include <algorithm>
  5. #include <iostream>
  6. using namespace std;
  7. const int N = 103, M = 50003;
  8. int n, m, w[N], p[N], c[N], ans;
  9. //w[i]: 体积; p[i]: 价值; c[i]: 数量
  10. int dp[M], qa[M], qb[M], la, lb, ra, rb;
  11. //qa: 正常队列; qb: 用来求最大值的辅助队列
  12. int main(){
  13. scanf("%d%d", &n, &m);
  14. for(int i = 1; i <= n; i++)
  15. scanf("%d%d%d", &w[i], &p[i], &c[i]);
  16. for(int i = 1; i <= n; i++){
  17. for(int j = 0; j < w[i]; j++){
  18. la = lb = 0, ra = rb = -1;//清空队列
  19. for(int k = 0; j + k * w[i] <= m; k++){
  20. if(ra - la + 1 > c[i])//如果队列中元素个数超过限制
  21. if(qb[lb] == qa[la++]) lb++; //从队首弹出
  22. int x = dp[j + k * w[i]] - k * p[i];
  23. qa[++ra] = x;
  24. while(rb >= lb && qb[rb] < x) rb--;
  25. qb[++rb] = x;
  26. dp[j + k * w[i]] = qb[lb] + k * p[i];
  27. ans = max(ans, dp[j + k * w[i]]);
  28. }
  29. }
  30. }
  31. printf("%d\n", ans);
  32. return 0;
  33. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注