单调栈:

单调栈可以用于找出数列中,某一个数它的左边或者右边第一个比它大或者小的数。我们关键是需要维护一个栈,栈中的元素必须是单调的,同时元素的下标也必须是单调的。

下标单调很简单,我们只需从左往右或者从右往左扫即可。

值的单调性,我们必须每次碰到一个数值的时候,询问栈顶元素,比较栈顶元素,假如不满足单调就弹栈,直到栈为空。

例题:leetcode 496

class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
stack<int> ms;
map<int,int> mm;
for(int i=0;i<(int)nums2.size();i++){
while(ms.size() && nums2[i]>ms.top()){
mm[ms.top()]=nums2[i];
ms.pop();
}
ms.push(nums2[i]);
}
vector<int> ans;
for(int i=0;i<(int)nums1.size();i++){
if(mm.count(nums1[i]))ans.push_back(mm[nums1[i]]);
else ans.push_back(-1);
}
return ans;
}
};

单调队列:

用来解决滑动窗口的最大值或者最小值。同样的,这里我们需要维护一个双端队列。

队列的值必须保证单调,同时下标也需要单调。

下标单调我们只要保证从左往右扫就可以了。值的单调,原理和上面的单调栈参不多,每次有新元素来,我们都需要比较队尾的元素,不满足单调性的队列尾 我们就去掉。

答案就是队列头的元素。

注意由于这里多了滑窗性质,所以我们还需要看看队列头的元素的位置是否在滑窗范围内。

洛谷 2032

#include <bits/stdc++.h>
using namespace std;
int main(){
vector<int> arrmv;
deque<int> q;
int n,k;cin>>n>>k;
for(int i=0;i<n;i++){
int t;cin>>t;
arrmv.push_back(t);
}
for(int i=0;i<n;i++){
int no=arrmv[i];

while(q.size() && arrmv[q.back()]<=no)q.pop_back();
q.push_back(i);

if(q.front()<i-k+1)q.pop_front();
//cerr<<"front "<<q.front()<<endl;
if(i>=k-1)cout<<arrmv[q.front()]<<endl;;
}

return 0;
}

双指针:

所谓双指针,就是用两个指针,其中一个往一个方向偏移的时候,另一个往往也会跟着移动,最后达到线性时间复杂度的操作。

洛谷 3143

#include <bits/stdc++.h>
using namespace std;
const int inf=1e9;
int main(){
int n,k;cin>>n>>k;
vector<int>arrmv (n);
for(int i=0;i<n;i++){
cin>>arrmv[i];
}
sort(arrmv.begin(),arrmv.end());
// for(auto it:arrmv)cerr<<it<<" ";
// cerr<<endl;
int i=0,j=0;
vector<int> pre(n);
for( j=0;j<n;j++){
while( abs(arrmv[i]-arrmv[j])>k)i++;
if(j)
pre[j]=max(pre[j-1],j-i+1);
else pre[j]=1;
}
// for(auto it:pre)cerr<<it<<" ";
// cerr<<endl;
reverse(arrmv.begin(),arrmv.end());
i=0,j=0;
vector<int> post(n);
for( j=0;j<n;j++){
while( abs(arrmv[i]-arrmv[j])>k)i++;
if(j)
post[j]=max(post[j-1],j-i+1);
else post[j]=1;
}
reverse(post.begin(),post.end());
int ans=-inf;
for(int i=0;i<n;i++){
if(i<n-1)
ans=max(ans,pre[i]+post[i+1]);
else ans=max(ans,pre[i]);
}
cout<<ans<<endl;
return 0;
}

二分:

当满足 true true true false false 结构时使用。

洛谷 1083 (本题需要差分区间加的方法)

#include <bits/stdc++.h>
using namespace std;
typedef struct{
int d,l,r;
}inp;
int main(){
int n,m;cin>>n>>m;
vector<int> arrmv(n+1,0);
for(int i=1;i<=n;i++)
cin>>arrmv[i];
vector<inp> in;
inp dummy;
in.push_back(dummy);
for(int i=0;i<m;i++){
inp t;
cin>>t.d>>t.l>>t.r;
in.push_back(t);
}
int x=1;int y=m+1;
while(x<y){
int mid=x+(y-x)/2;
vector<int> dif(n+2,0);
for(int i=1;i<=mid;i++){
dif[in[i].l]-=in[i].d;
dif[in[i].r+1]+=in[i].d;
}
for(int i=1;i<=n;i++){
dif[i]+=dif[i-1];

}
vector<int> tmp=arrmv;
int suc=1;
for(int i=1;i<=n;i++){
tmp[i]+=dif[i];
if(tmp[i]<0){
suc=0;
break;
}
}
if(suc){
x=mid+1;
}else y=mid;

}
if(y==m+1){
cout<<0<<endl;
}else{
cout<<-1<<endl;
cout<<x<<endl;
}
return 0;
}

有时候,我们需要求出一个单调函数 最接近某一个值的下标时,这时候可以用二分,假设fx是递增的 找到f(x)<=target 的最大的下标x1 找到f(x)>=target 的最小的下标x2.

#include <bits/stdc++.h>
#define int long long
using namespace std;
int n;
int m,s;
const int MAXN=2e5+10;
vector<int> v(MAXN);
vector<int> w(MAXN);
vector<pair<int,int>> range(MAXN);
int calc(int mid){
// cerr<<mid<<endl;
vector<int> tmpv(n+1,0);
vector<int> tmpno(n+1,0);
for(int i=1;i<=n;i++){
if(w[i]>=mid){
tmpv[i]=v[i];
tmpno[i]=1;
}
}
for(int i=1;i<=n;i++){
tmpv[i]+=tmpv[i-1];
tmpno[i]+=tmpno[i-1];
}
int tmpy=0;
for(int i=1;i<=m;i++){
int l=range[i].first;
int r=range[i].second;
tmpy+=(tmpv[r]-tmpv[l-1]) * (tmpno[r]-tmpno[l-1]);
}
// cerr<<tmpy<<endl;
return tmpy;
}
int32_t main(){
cin>>n>>m>>s;
int maxw=-1;

for(int i=1;i<=n;i++)
{cin>>w[i]>>v[i];
maxw=max(maxw,w[i]);
}

for(int i=1;i<=m;i++){
cin>>range[i].first>>range[i].second;
}
int x=0;int y=maxw+1;
while(x+1<y){
int mid=x+(y-x)/2;
int tmpy=calc(mid);
if(tmpy>=s)x=mid;
else y=mid;
}
int lx=x;
x=0; y=maxw+1;
// cerr<<"next"<<endl;
while(x+1<y){
int mid=x+(y-x)/2;
int tmpy=calc(mid);
if(tmpy>s)x=mid;
else y=mid;
}
// interesting
int ly=y; //template has some problems check csdn
// cerr<<lx<<" "<<ly<<endl;
cout<<min(labs(s-calc(lx)),labs(s-calc(ly)))<<endl;;

return 0;
}

洛谷 1948

(二分 01构造 最短路)

#include <bits/stdc++.h>
using namespace std;
const int MAXN=1010;
vector<vector<pair<int,int>>> gra(MAXN);
const int INF=1e9;
int main(){
int n,m,k;cin>>n>>m>>k;
int y,x;
x=0;
y=-1;
for(int i=1;i<=m;i++){
int a,b,c;cin>>a>>b>>c;
gra[a].emplace_back(make_pair(b,c));
gra[b].emplace_back(make_pair(a,c));
y=max(y,c);
}
y+=1;
int dist[MAXN];
while(x<y){
int mid=x+(y-x)/2;
int u=1;int desc=n;

for(int i=0;i<MAXN;i++)dist[i]=INF;
dist[u]=0;
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> pq;
pq.push(make_pair(dist[u],u));
while(pq.size()){
pair<int,int> front=pq.top();pq.pop();
int u=front.second;
if(front.first>dist[u])continue;
for(int i=0;i<(int)gra[u].size();i++){
pair<int,int> ii=gra[u][i];
int nx=ii.first;
int wei=ii.second;
if(wei>mid)wei=1;
else wei=0;
if(dist[u]+wei<dist[nx]){
dist[nx]=dist[u]+wei;
pq.push(make_pair(dist[nx],nx));
}
}
}
if(dist[desc]==INF){
cout<<-1<<endl;
return 0;
}else{
if(dist[desc]<=k)y=mid;
else x=mid+1;
}
}
assert(x==y);
cout<<x<<endl;
return 0;
}

洛谷 P1525 (二分 + 偶图判定)

#include <bits/stdc++.h>
using namespace std;
int suc;
const int MAXN=2e4+10;
int flag[MAXN];
vector<vector<int>> gra(MAXN);
void dfs(int u,int level){
flag[u]=level%2;
for(int i=0;i<(int)gra[u].size();i++){
int nx=gra[u][i];
if(flag[nx]==-1)dfs(nx,level+1);
else if(flag[nx]==flag[u]){
suc=0;
return ;
}
}
}
int main(){
int n,m;cin>>n>>m;
vector<vector<pair<int,int>>> edges(n);
for(int i=0;i<m;i++){
int a,b,c;cin>>a>>b>>c;
a-=1;b-=1;
edges[a].emplace_back(make_pair(b,c));
}
int x=0,y=1e9+10;
while(x<y){
int m=x+(y-x)/2;
gra=vector<vector<int>> (MAXN);
set<int> ms;
for(int i=0;i<n;i++){
for(int j=0;j<(int)edges[i].size();j++){
pair<int,int> ii=edges[i][j];
if(ii.second>m){
ms.insert(i);
ms.insert(ii.first);
gra[i].emplace_back(ii.first);
gra[ii.first].emplace_back(i);
}
}
}
memset(flag,-1,sizeof(flag));
suc=1;
for(int u=0;u<n;u++){
if(ms.count(u)&&flag[u]==-1)dfs(u,0);
}
if(suc)y=m;
else x=m+1;
}
assert(x==y);
cout<<x<<endl;
return 0;
}

洛谷P2680 (二分答案,LCA,树上差分)

// Sparse Matrix DP approach to find LCA of two nodes 
#include <bits/stdc++.h>
#define OPEN 0
#define RG register
using namespace std;
const int MAXN = 3e5 + 10;
typedef struct{
int to,nx,w;
}gra;
typedef struct{
int u,v,lca,len;
}mys;
int n, m;
mys len[MAXN];
int cnt=0;
gra ori[MAXN];
gra que[MAXN];
int dif[MAXN];
int head[MAXN];
int f[MAXN];
int dis[MAXN];
int maxlen=-1;
bool vis[MAXN];
int back_edge_w[MAXN];
void add_edge(int u,int v,int w=0){
ori[++cnt].to=v;
ori[cnt].nx = head[u];
ori[cnt].w=w;
head[u]=cnt;
}
int mycount;
int qcnt;
int headq[MAXN];
void qadd_edge(int u,int v){
que[++qcnt].nx=head[qcnt];
que[qcnt].to=v;
headq[u]=qcnt;
}
int dis_find(int u){
if(f[u]==u)return u;else return f[u]=dis_find(f[u]);
}
int suc;
int dfs(int u,int prev,int tar){
for(int i=head[u];i;i=ori[i].nx){
int nx=ori[i].to;
if(nx==prev)continue;
dif[u]+=dfs(nx,u,tar);
}
if(dif[u]>=mycount && maxlen - back_edge_w[u]<=tar){
suc=1;
}
return dif[u];
}
bool check(int mid) {
memset(dif, 0, sizeof(dif));
mycount = 0;


for (int i = 1; i<=m; i++) {
if (len[i].len>mid) {
mycount++;
dif[len[i].u] += 1;
dif[len[i].v] += 1;
dif[len[i].lca] -= 2;
}
}
suc=0;
dfs(1,0,mid);
if(suc)return true;
else return false;
}
inline int read()
{
RG char c = getchar(); RG int x = 0;
while (c<'0' || c>'9') c = getchar();
while (c >= '0'&&c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
return x;
}
void tarjan(int u,int pre) //tarjan??????
{
for(int i=head[u];i;i=ori[i].nx){
int v=ori[i].to;
if(v==pre)
continue;
dis[v]=dis[u]+ori[i].w;
tarjan(v,u);
back_edge_w[v]=ori[i].w;
int f1=dis_find(v);
int f2=dis_find(u);
if(f1!=f2)
f[f1]=dis_find(f2);
vis[v]=1;
}
for(int i=headq[u];i;i=que[i].nx)
if(vis[que[i].to])
{
int p=(i+1)>>1;
len[p].lca=dis_find(que[i].to);
len[p].len=dis[u]+dis[que[i].to]-2*dis[len[p].lca];
maxlen=max(maxlen,len[p].len);
}
}
int main()
{
#if OPEN
freopen("vsin.txt", "r", stdin);
#endif
n=read();m=read();
cnt=0;
for(int i=0;i<MAXN;i++)f[i]=i;
for (int i = 0; i<n - 1; i++) {
int a, b, c;
a=read();b=read();c=read();
add_edge(a,b,c);
add_edge(b,a,c);
}
int x = 0; int y = -1;
qcnt=0;
for (int i = 1; i<=m; i++) {

int u=read();
int v=read();
qadd_edge(u,v);
qadd_edge(v,u);
len[i].u=u;
len[i].v=v;
}
tarjan(1,0);
y =maxlen+1;
while (x <y) {
int mid = x + (y - x) / 2;
if (check(mid))y = mid;
else x = mid+1;
}
printf("%d\n",y);
return 0;
}

有时候,对于排序类的问题,可以把某些大于一个常数的数为1,小于某个常数为0. 这样做的好处是,对于01序列我们可以用rsq线段树来维护排序。另外这个常数我们也可以用二分来查找。

洛谷P2824 

#include <bits/stdc++.h>
#define left(x) (x<<1)
#define right(x) ((x<<1)+1)
using namespace std;
vector<int> A;
const int MAXN=1e5*4;

int st[MAXN];
int lazy[2*MAXN];
int build (int root, int l,int r){
if(l==r)return st[root]=A[l];
int m=l+(r-l)/2;
int lv=build(left(root),l,m);
int rv=build(right(root),m+1,r);
// cerr<<lv<<" "<<rv<<endl;
return st[root]=lv+rv;
}
int query(int root ,int l,int r,int ql ,int qr){
// cerr<<l<<" "<<r<<" "<<ql<<" "<<qr<<" "<<endl;
if(ql>qr)return 0;
if(lazy[root]!=-1){
// cerr<<"lazy"<<endl;
st[root]=(r-l+1)*lazy[root];
lazy[left(root)]=lazy[root];
lazy[right(root)]=lazy[root];
lazy[root]=-1;
}
if(l>=ql && r<=qr){return st[root];}
if(l>qr || r<ql){
return 0;
}
int m=l+(r-l)/2;
int lv=query(left(root),l,m,ql,qr);
int rv=query(right(root),m+1,r,ql,qr);
return lv+rv;
}
int update(int root ,int l,int r,int ql,int qr,int v){
if(ql > qr)return 0;
if(lazy[root]!=-1){
st[root]=(r-l+1)*lazy[root];
lazy[left(root)]=lazy[root];
lazy[right(root)]=lazy[root];
lazy[root]=-1;
}
if(l>=ql && r<=qr){
st[root]=(r-l+1)*v;
lazy[left(root)]=v;
lazy[right(root)]=v;
return st[root];
}
if(l>qr || r<ql){
return st[root];
}
int m=l+(r-l)/2;
int lv=update(left(root),l,m,ql,qr,v);
int rv=update(right(root),m+1,r,ql,qr,v);
return st[root]=lv+rv;
}

int main(){
int n,m;cin>>n>>m;
A.assign(n,0);
vector<int> arrmv(n,0);
for(int i=0;i<n;i++)cin>>arrmv[i];
vector<int> L(m);
vector<int> R(m);
vector<int> C(m);
for(int i=0;i<m;i++){
cin>>C[i]>>L[i]>>R[i];
L[i]-=1;R[i]-=1;
}
int pos;cin>>pos;
pos-=1;
vector<int> tmp=arrmv;
sort(tmp.begin(),tmp.end());
int x=0;int y=tmp.back()+10;
while(x<y){
int mid=x+(y-x)/2;
for(int i=0;i<n;i++){
if(arrmv[i]<tmp[mid])A[i]=0;
else A[i]=1;
}

memset(lazy,-1,sizeof(lazy));
int root=1;
build(root,0,n-1);
for(int i=0;i<m;i++){
int ret=query(root,0,n-1,L[i],R[i]);
// cerr<<"ret "<<ret<<endl;
if(C[i]){

update(root,0,n-1,L[i],L[i]+ret-1,1);
update(root,0,n-1,L[i]+ret,R[i],0)
;
}else{
int ls=R[i]-L[i]+1-ret;
update(root,0,n-1,L[i],L[i]+ls-1,0);
update(root,0,n-1,L[i]+ls,R[i],1);

}
}
int que=query(root,0,n-1,pos,pos);
// cerr<<que<<endl;
if(que)x=mid+1;
else y=mid;
}
//assert(x==y);
cout<<y<<endl;
return 0;
}

有时候我们考虑使用二分的思想来思考问题。使用这种思想作为切入点,最后可能发现是用倍增或者其它思想,关键是我们需要从中得到启发。

倍增:

倍增指的是信息满足从二进制角度看的结合律。例如:

洛谷网课第三节_i++

其中

洛谷网课第三节_ci_02

 表示从位置p开始走2^i步。注意在更新table时候,i是放在for的最外层更新。

洛谷P1613 (通过倍增来连接新的边)

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=100;
const int MAXK=65;
bool f[MAXK][MAXN][MAXN];
int dist[MAXN][MAXN];
int n,m;

int32_t main(){
cin>>n>>m;
for(int i=0;i<MAXN;i++)
for(int j=0;j<MAXN;j++)dist[i][j]=1e18;
for(int i=0;i<m;i++){
int u,v;cin>>u>>v;
f[0][u][v]=true;
dist[u][v]=1;
}
for(int k=0;k<64;k++){
for(int v=1;v<=n;v++){
for(int u=1;u<=n;u++){
for(int w=1;w<=n;w++){
if(f[k][u][v]&&f[k][v][w]){
f[k+1][u][w]=true;
dist[u][w]=1;
}
}
}

}
}
for(int v=1;v<=n;v++)
for(int u=1;u<=n;u++)
for(int w=1;w<=n;w++)
dist[u][w]=min(dist[u][w],dist[u][v]+dist[v][w]);
cout<<dist[1][n]<<endl;
return 0;
}

洛谷4155(贪心,双指针,倍增)

注意这里有一个小技巧,周期循环我们需要破成链。

#include <bits/stdc++.h>
#define RG register
#define OPEN 0
using namespace std;
// in
const int MAXN = (2e5 + 10);
int dp[21][2 * MAXN];
inline int read()
{
RG char c = getchar(); RG int x = 0;
while (c<'0' || c>'9') c = getchar();
while (c >= '0'&&c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
return x;
}
typedef struct {
unsigned int id, l, r;
void s(int id, int l, int r) {
id = id; l = l; r = r;
}
}s;
bool cmp(s &fir, s &las) {
if (fir.l<las.l)return true;
else return false;
}
vector<s> arrmv(2 * MAXN);
int32_t main() {
#if OPEN
freopen("vsin.txt", "r", stdin);
#endif
long long n, m; n = read(); m = read();

s tmp;
for (int i = 0; i<n; i++) {
long long l, r;
l = read(); r = read();
tmp.id = i;
tmp.l = l;
if (r<l)r += m;
tmp.r = r;
arrmv[i]=(tmp);

tmp.l = arrmv[i].l + m;
tmp.r = arrmv[i].r + m;
tmp.id = arrmv[i].id;
arrmv[i + n] = tmp;
}

sort(arrmv.begin(), next(arrmv.begin(),2*n), cmp);
memset(dp, -1, sizeof(dp));

for (int i = 0,p=i; i<2*n; i++) {

while (p<2 * n && arrmv[p].l<=arrmv[i].r)p++;
dp[0][i] = p - 1;
}

for (int i = 1; i <= 20; i++)
for (int p = 0; p<2 * n; p++) {
if (dp[i - 1][p]==-1 || dp[i - 1][dp[i - 1][p]]==-1|| arrmv[dp[i - 1][dp[i - 1][p]]].l <= arrmv[p].l)break;
else dp[i][p] = dp[i - 1][dp[i - 1][p]];
}
long long ans = 0;
vector<long long> pri(n);
for (int p = 0; p<n; p++){
int agent = p;
ans = 0;
for (int i = 20; i >= 0;i--){
if (dp[i][agent]==-1 || arrmv[dp[i][agent]].l>=arrmv[p].l+m ) {
continue;
}
ans += (1ll << i);
agent = dp[i][agent];
}
ans += 1ll;
pri[arrmv[p].id] = ans;
}
for (int i = 0; i<n; i++)printf("%lld ",pri[i]);
printf("\n");
return 0;
}