这几天的知识学习比较多,因为时间不够了。加油吧,为了梦想。

这里写几条简单的单调栈作为题解记录,因为单调栈的用法很简单,可是想到并转化成用这个需要一些题目的积淀。

相关博客参见:https://blog.csdn.net/wubaizhe/article/details/70136174

POJ 3250 Bad Hair Day

题意与分析

题意是这样的:\(n\)个牛排成一列向右看,牛\(i\)能看到牛\(j\)的头顶,当且仅当牛\(j\)在牛\(i\)的右边并且牛\(i\)与牛\(j\)之间的所有牛均比牛\(i\)矮。设牛\(i\)能看到的牛数为\(C_i\),求\(\Sigma C_i\)。

分析:注意到没有?每个牛只能向右看,看到的都是比自己矮的等价于每头牛能被自己左边且比自己大的牛看见,这是啥?单调啊!维护一个单调栈,确定每头牛左边有几头牛比自己高的,然后求和即可。

代码

/*
* Filename: poj3250.cpp
* Date: 2018-11-13
*/ #include <iostream>
#include <cstring>
#include <stack>
#define INF 0x3f3f3f3f
#define PB push_back
#define MP make_pair
#define fi first
#define se second
#define rep(i,a,b) for(repType i=(a); i<=(b); ++i)
#define per(i,a,b) for(repType i=(a); i>=(b); --i)
#define ZERO(x) memset(x, 0, sizeof(x))
#define MS(x,y) memset(x, y, sizeof(x))
#define ALL(x) (x).begin(), (x).end() #define QUICKIO \
ios::sync_with_stdio(false); \
cin.tie(0); \
cout.tie(0);
#define DEBUG(...) fprintf(stderr, __VA_ARGS__), fflush(stderr) using namespace std;
typedef long long ll;
typedef int repType;
int
main()
{
QUICKIO
int n;
while(cin>>n)
{
ll sum=0;
stack<ll> s;
ll h,t;
cin>>h;
s.push(h);
rep(i,1,n-1)
{
cin>>t;
while(!s.empty() && t>=s.top()) s.pop();
sum+=s.size();
s.push(t);
}
cout<<sum<<endl;
}
return 0;
}

POJ 2796 Feel Good

题意与分析

题意是这样的:一个数组,对于某个区间,这个区间的和*这个区间中的最小值=这个区间的计算值。求这个数组中的最大计算值,并任意输出其的一个左右位置。

这题很容易想到我之前写的一题:Codeforces Round #305 Div. 2 D——我们需要维护某个数分别是哪些区间的最小值。又注意到这个数组是非负的,那么肯定是越大越好。预先维护一个前缀和,然后\(O(n)\)扫一遍完事了。

这两题给我们一个启示:单调栈可以维护区间最小值(目前看来是离线的情况下),通过维护左边最右的比它小的值的和右边最左的比它小的值。

代码

/*
* Filename: poj2796.cpp
* Date: 2018-11-14
*/ #include <iostream>
#include <cstring>
#include <stack>
#include <cstdio> #define INF 0x3f3f3f3f
#define PB emplace_back
#define MP make_pair
#define fi first
#define se second
#define rep(i,a,b) for(repType i=(a); i<=(b); ++i)
#define per(i,a,b) for(repType i=(a); i>=(b); --i)
#define ZERO(x) memset(x, 0, sizeof(x))
#define MS(x,y) memset(x, y, sizeof(x))
#define ALL(x) (x).begin(), (x).end() #define QUICKIO \
ios::sync_with_stdio(false); \
cin.tie(0); \
cout.tie(0);
#define DEBUG(...) fprintf(stderr, __VA_ARGS__), fflush(stderr) using namespace std;
typedef long long ll;
typedef int repType; const int MAXN=100005;
int n,l[MAXN],r[MAXN];
int arr[MAXN];
ll prefix[MAXN];
int main()
{
while(scanf("%d",&n)==1)
{
prefix[0]=0;
rep(i,1,n)
{
scanf("%d",&arr[i]);
prefix[i]=prefix[i-1]+arr[i];
}
stack<int> s;
rep(i,1,n)
{
while(!s.empty() && arr[s.top()]>=arr[i])
s.pop();
if(s.empty()) l[i]=0;
else l[i]=s.top();
s.push(i);
}
s=stack<int>();
per(i,n,1)
{
while(!s.empty() && arr[s.top()]>=arr[i])
s.pop();
if(s.empty()) r[i]=n+1;
else r[i]=s.top();
s.push(i);
}
/*
rep(i,1,n)
{
cout<<l[i]<<" "<<r[i]<<endl;
}
*/
ll ans=-1;
int lp=1,rp=1;
rep(i,1,n)
{
ll presum=prefix[r[i]-1]-prefix[l[i]];
//cout<<arr[i]*presum<<endl;
if(ans<arr[i]*presum)
{
ans=arr[i]*presum;
lp=l[i]+1;
rp=r[i]-1;
}
}
printf("%lld\n%d %d\n", ans, lp, rp);
}
return 0;
}

HDU 1506 Largest Rectangle in a Histogram

题意与分析

题意其实就是要找到一个尽可能大的矩形来完全覆盖这个矩形下的所有柱子,只能覆盖柱子,不能留空。每一个柱子都要尽可能向左向右延伸,使得获得最大的面积。

为什么要把这几题连起来写呢?发现没有,这几题各种套路,看似不同,但是转化后思路(联动上面那个Codeforce题目)一模一样!都是去维护一个区间最小值。这里怎么维护呢?考虑以自己为min,扩展的最大区域即可,然后扫一遍\(O(n)\)完事了(Codeforces题目的结论,区间长度为\(r_i-l_i-1\)),哈哈。

代码

/*
* Filename: hdu1506.cpp
* Date: 2018-11-14
*/ #include <iostream>
#include <cstring>
#include <stack>
#include <cstdio> #define INF 0x3f3f3f3f
#define PB emplace_back
#define MP make_pair
#define fi first
#define se second
#define rep(i,a,b) for(repType i=(a); i<=(b); ++i)
#define per(i,a,b) for(repType i=(a); i>=(b); --i)
#define ZERO(x) memset(x, 0, sizeof(x))
#define MS(x,y) memset(x, y, sizeof(x))
#define ALL(x) (x).begin(), (x).end() #define QUICKIO \
ios::sync_with_stdio(false); \
cin.tie(0); \
cout.tie(0);
#define DEBUG(...) fprintf(stderr, __VA_ARGS__), fflush(stderr) using namespace std;
typedef long long ll;
typedef int repType; const int MAXN=100005;
int n,l[MAXN],r[MAXN];
ll arr[MAXN];
int main()
{
while(scanf("%d",&n)==1)
{
if(n==0) break;
rep(i,1,n)
scanf("%lld",&arr[i]);
stack<int> s;
rep(i,1,n)
{
while(!s.empty() && arr[s.top()]>=arr[i])
s.pop();
if(s.empty()) l[i]=0;
else l[i]=s.top();
s.push(i);
}
s=stack<int>();
per(i,n,1)
{
while(!s.empty() && arr[s.top()]>=arr[i])
s.pop();
if(s.empty()) r[i]=n+1;
else r[i]=s.top();
s.push(i);
}
/*
rep(i,1,n)
{
cout<<l[i]<<" "<<r[i]<<endl;
}
*/
ll ans=-1;
rep(i,1,n)
{
ans=max(ans,arr[i]*(r[i]-l[i]-1));
}
printf("%lld\n", ans);
}
return 0;
}

HDU 5033 Building

题意与分析

题意:有一个人在到处是高楼大厦的地方抬头仰望,假设所有高楼都在一条数轴上,给出了高楼的位置和高度,然后给出了人的位置(高度为0.....),问人能看到的阳光的最大角度是多少。

这题看起来和前面不太一样了,角度而不是最小值区间,可是想不到吧,这也可以单调栈2333

怎么写呢?容易想到,决定一个人的视角的是他左右边的建筑物之间的斜率。如果不容易想到的话,可以参见 https://www.cnblogs.com/chen9510/p/7246889.html 的文章,里面有几幅图片比较生动的表现了这个结论。

然后就是好玩的地方了:我们要在读入所有的查询后,将每个查询点视为高度为0的楼,然后再通过比较两栋相邻楼顶连线的斜率大小维护一个单调栈。这样写就和上面的套路一样一样的了,哈哈。你会从代码中发现这一点。

代码

自闭了,自己的代码死活找不到问题- -拿别人的代码出来学习吧

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn=1e5+5;
const double PI=acos(-1);
struct Node{
double x;
double y;
int L,R,id;
}a[2*maxn];
int top,s[maxn];
bool cmp(const Node s1,const Node s2)
{
return s1.x<s2.x;
}
bool cmp2(const Node s1,const Node s2)
{
return s1.id<s2.id;
}
double cal2(double x,double y)
{
//cout<<"H:"<<x<<" "<<y<<endl;
return atan(y/x);
}
int main()
{
///cout << "Hello world!" << endl;
int T,Case=1; cin>>T;
while(T--)
{
printf("Case #%d:\n",Case++);
int N; scanf("%d",&N);
for(int i=1;i<=N;i++) scanf("%lf%lf",&a[i].x,&a[i].y),a[i].id=i;
int Q; scanf("%d",&Q);
for(int i=N+1;i<=N+Q;i++) scanf("%lf",&a[i].x),a[i].y=0,a[i].id=i;
sort(a+1,a+N+Q+1,cmp); ///for(int i=1;i<=N+Q;i++) cout<<a[i].id<<" "<<a[i].x<<" "<<a[i].y<<endl;
top=0; s[0]=1;
for(int i=2;i<=N+Q;i++)
{
while(1)
{
if(top<1) break;
double x1=a[s[top]].x-a[s[top-1]].x;
double y1=a[s[top]].y-a[s[top-1]].y;
//double f1=cal(a[s[top-1]].x, a[s[top-1]].y, a[s[top]].x, a[s[top]].y);
double x2=a[i].x-a[s[top]].x;
double y2=a[i].y-a[s[top]].y;
//double f2=cal(a[s[top]].x, a[s[top]].y, a[i].x, a[i].y);
if(y1*x2>y2*x1) break;
top--;
}
if(a[i].id>N) a[i].L=a[s[top]].id;//,cout<<"top1 "<<top<<" "<<s[top]<<" "<<a[s[top]].id<<endl;
s[++top]=i;
} top=0; s[0]=N+Q;
for(int i=N+Q-1;i>=1;i--)
{
while(1)
{
if(top<1) break;
double x1=a[s[top-1]].x-a[s[top]].x;
double y1=a[s[top-1]].y-a[s[top]].y;
//double f1=cal(a[s[top-1]].x, a[s[top-1]].y, a[s[top]].x, a[s[top]].y);
double x2=a[s[top]].x-a[i].x;
double y2=a[s[top]].y-a[i].y;
//double f2=cal(a[s[top]].x, a[s[top]].y, a[i].x, a[i].y);
if(y1*x2<y2*x1) break;
top--;
}
if(a[i].id>N) a[i].R=a[s[top]].id;//,cout<<"top2 "<<top<<" "<<s[top]<<" "<<a[s[top]].id<<endl;
s[++top]=i;
} sort(a+1,a+N+Q+1,cmp2);
for(int i=N+1;i<=N+Q;i++)
{
//cout<<"--->"<<a[i].L<<" "<<a[i].R<<endl;
double ans=PI;
int k=a[i].L; ans-=cal2(a[i].x-a[k].x,a[k].y);
k=a[i].R; ans-=cal2(a[k].x-a[i].x,a[k].y);
printf("%.10f\n",ans*180/PI);
}
}
return 0;
}

总结

只要你能转化成离线的区间最值问题,那么单调栈有可能能够维护它(当然,问题需要满足一定的性质——这就是我们需要干的活了:直觉、转化、or玄学)。

「日常训练&知识学习」单调栈的更多相关文章

  1. 「日常训练&知识学习」树的分块(王室联邦,HYSBZ-1086)

    题意与分析 这题的题意就是树分块,更具体的看题目(中文题). 学习这一题是为了树的分块,为树上莫队做铺垫. 参考1:https://blog.csdn.net/LJH_KOQI/article/det ...

  2. 「日常训练&知识学习」莫队算法(二):树上莫队(Count on a tree II,SPOJ COT2)

    题意与分析 题意是这样的,给定一颗节点有权值的树,然后给若干个询问,每次询问让你找出一条链上有多少个不同权值. 写这题之前要参看我的三个blog:Codeforces Round #326 Div. ...

  3. 「日常训练&知识学习」树的直径(POJ-1849,Two)

    题意 一个城市由节点和连接节点的街道组成,街道是双向的. 此刻大雪覆盖了这个城市,市长确定了一些街道要将它们清扫干净,这些街道保证所有的节点可以通过它们连通而且街道数目尽可能小. 现有两台相同的扫雪机 ...

  4. 「国庆训练&知识学习」图的最大独立集与拓展(Land of Farms,HDU-5556)

    题意 一个\(N*M\)的矩阵,其中"."代表空地,"0-9"代表古代建筑,我们如果选择了一个编号的古代建筑想要建立,那么对应就要将全部该编号的建筑建立起来,如 ...

  5. 「日常训练」Caterpillar(POJ-3310)

    题意与分析 一条很有趣的题目.给一个无向图,问它是否无环,且可以在上面找到一条线,使所有的顶点要么在线上要么不在线上但在与线相连的边上. 那么首先要确定所有点联系在一起.这个可以同判环一起处理:如果建 ...

  6. 「日常训练」ZgukistringZ(Codeforces Round #307 Div. 2 B)

    题意与分析(CodeForces 551B) 这他妈哪里是日常训练,这是日常弟中弟. 题意是这样的,给出一个字符串A,再给出两个字符串B,C,求A中任意量字符交换后(不限制次数)能够得到的使B,C作为 ...

  7. 「日常训练」Mike and Feet(Codeforces Round #305 Div. 2 D)

    题意 (Codeforces 548D) 对一个有$n$个数的数列,我们要求其连续$x(1\le x\le n)$(对于每个$x$,这样的连续group有若干个)的最小数的最大值. 分析 这是一道用了 ...

  8. 「日常训练」 Fire!(UVA-11624)

    与其说是训练不如说是重温.重新写了Java版本的代码. import java.util.*; import java.math.*; import java.io.BufferedInputStre ...

  9. 「日常训练」Battle Over Cities - Hard Version(PAT-TOP-1001)

    题意与分析 题意真的很简单,实在不想讲了,简单说下做法吧. 枚举删除每个点,然后求最小生成树,如果这个路已经存在那么边权就是0,否则按照原来的处理,之后求花费,然后判整个图是否联通(并查集有几个roo ...

随机推荐

  1. PHP代码的多继承 -》 PHP代码复用新的姿势 trait

    本文参考:  http://php.net/language.oop5.traits 一.什么是trait 从PHP 5.4.0 开始 PHP 实现了一种新的代码复用方式 trait. 二.trait ...

  2. 【转】maven命令-P 参数引发的思考

    序言: maven 命令:clean package -Dmaven.test.skip=true -P product 1.命令很简单是:清class文件,打包构建,跳过测试,注意最后一个 -P p ...

  3. sendmail启动报错

    sendmail启动不了,报错如下: 解决方法: 在/etc/mail/sendmail.cf 配置文件中查找 Dj$w,并在此行下面增加这一行. Dj$w. 在/etc/hosts 增加一行 192 ...

  4. 【题解】洛谷P2023 [AHOI2009] 维护序列(线段树)

    洛谷P2023:https://www.luogu.org/problemnew/show/P2023 思路 需要2个Lazy-Tag 一个表示加的 一个表示乘的 需要先计算乘法 再计算加法 来自你谷 ...

  5. hdu 2098 分拆素数和(一个偶数拆分成两个不同素数和 拆法数量)

    传送门: http://acm.hdu.edu.cn/showproblem.php?pid=2098 分拆素数和 Time Limit: 1000/1000 MS (Java/Others)     ...

  6. webAPI请求消息过滤器

    每当制作一个WebAPI,就必然会收到外部很多调用这个API的请求,有时候,我们希望,能从这些外部来的请求中,按照一定条件筛选过滤一下,只将那些我们觉得合法的,有必要回应的请求放进来,一方面挡住那些非 ...

  7. vue使用iview Timeline 时间轴不显示问题

    vue Timeline 时间轴不显示渲染的效果 官网代码 <Timeline pending> <TimelineItem>发布1.0版本</TimelineItem& ...

  8. stylus(css预编译器)

    推荐去张鑫旭大神这里详细了解:http://www.zhangxinxu.com/jq/stylus/ 安装 npm install -g stylus 自动编译 $ stylus -w demo.s ...

  9. Colored Boots题解

    题目来自Codeforce 1141Dhttp://codeforces.com/problemset/problem/1141/D 因为是全英文题面,就先简单的阐述一下题面. 首先输入一个数n,然后 ...

  10. 断言assert()与调试帮助

    列表内容assert()是一种预处理宏(preprocessor marco),使用一个表达式来作为条件,只在DEBUG模式下才有用. assert(expr); 对expr求值,如果expr为假,则 ...