Learning notes on monotone stack problem of segment tree maintenance

Put reference materials first
Senior blog 1
Senior blog 2
Yubai's blog
Pink rabbit's blog
First, find out what this thing does
In some problems, there will be a model similar to monotone stack, and this problem usually has more than one or dynamic monotone stack. Sometimes it will let you make statistics and directly explode the complexity. It usually requires magical operations such as cdq divide and conquer and line segment tree, and the method we want to say can be solved in \ (nlog^2 \) time and has universality

outline

The common segment tree is that each node maintains the information of an interval, which is directly merged when \ (pushup \)
Generally, the information in this topic cannot be merged directly, because the information in an interval only considers the restriction of one interval, which may not be tenable due to the increase of restriction
At this time, we should introduce a function to implement the process of merging information in \ (push up \), and the complexity of this function should not be too high
We save two fields in the segment tree: \ (data \) and \ (ans \), one for additional information and the other for answers
\The information stored in the (data \) field is characterized by:
1. Generally, it is the information directly given in the title, but it has a restrictive effect on the answer statistics
2. There is nothing to limit it, so it can be pushed up like an ordinary line segment tree
\The characteristics of (ans \) domain storage information are:
1. Most of them are statistical answers, but due to the restrictions received, they cannot be counted directly
2. Indicates the answer of an interval when it meets the limit of this interval, so it may change during merging
There are two ways to implement this function, and the boss's blog is also very clear
The first is to find the answer of this interval after considering the limit of this interval

Let's analyze. If we get to the leaf, we can directly judge whether it meets the limit, that is, whether it contributes
Since we want to make a monotonic stack, if it is monotonic increasing now, see whether the left subtree is full or not
If the left is satisfied, the right is also satisfied according to the monotonicity, so the answer on the right is returned directly (after processing), and then recursive on the left
Otherwise, the left side will be completely bounced, and the right side will be recursive directly
Obviously, the complexity is \ (logn \) at one side of each recursion
In fact, this writing method has limitations, because it is only suitable for the answer statistics that meet the additivity. If it is difficult to do things like \ (max \), there will be a second board
The second is the answer of the other half when only half of the current interval is considered

It is found that the difference is directly added, which is the advantage of our definition
The rest is basically the same as the ordinary segment tree, but recurse the subtree in order according to the monotonicity of the monotone stack

Building reconstruction

This is the problem of satisfying additivity. You can use the first board (me back then)
It is the longest sequence with monotonically increasing slope. Maintain each point separately and put the ugly code of the early years

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=100050;
struct node{
	int l,r,len;
	double ma;
}a[4*N];
inline void qi(int id)
{
	a[id].ma=max(a[id*2].ma,a[id*2+1].ma);	 
} 
inline int pushup(double mx,int id)
{
	if(a[id].ma<=mx)return 0;
	if(a[id].l==a[id].r)
	{
		if(a[id].ma<=mx)return 0;
		else return 1;
	}
	if(a[id*2].ma<=mx)return pushup(mx,id*2+1);
	else return pushup(mx,id*2)+a[id].len-a[id*2].len; 
}
inline void build(int id,int l,int r)
{
	a[id].l=l;a[id].r=r;
	if(l==r)return;
	int mid=(l+r)>>1;
	build(id*2,l,mid);build(id*2+1,mid+1,r);
}
inline void change(int id,int p,double v)
{
	if(a[id].l==a[id].r)
	{
		a[id].ma=v;a[id].len=1;
		return;
	}
	int mid=(a[id].l+a[id].r)>>1;
	if(p<=mid)change(id*2,p,v);
	else change(id*2+1,p,v);
	qi(id);
	a[id].len=a[id*2].len+pushup(a[id*2].ma,id*2+1);
}
signed main()
{
	int n,m;cin>>n>>m;
	build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		int x,y;scanf("%lld%lld",&x,&y);
		double v=y/(double)x;
		change(1,x,v);
		printf("%lld\n",a[1].len); 
	 } 
	return 0;
}

Tao Tao picking apples

This supports modification. When you modify it first, you modify the answer, so just modify it directly \ (push up \)
Because I read blog Hu, I don't have a code

Maintenance dp

The common form of this problem is dp, and there are not many direct questions
First of all, I want to write a dp equation. The basic transfer is obvious, but there are many additional restrictions
Think about how to realize the additional restriction. In most cases, the additional restriction and the original sequence are a mapping. One idea is to flip the coordinate system, so that there is less restriction through interval query
Then use the above board to realize maintenance transfer

God knows

Just now, this is the maintenance fetch \ (max \) transfer
Instead of setting up restrictions at the beginning, I insert restrictions every time to ensure that the transfer is correct, because a \ (f \) certainly does not need to consider its subsequent restrictions
When querying, the \ (v \) will continue to increase. Just open a global variable to save. Each query is preceded by - 1


#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=250050;
int a[N],f[N],w[N],b[N],n;
struct tree{
	int l,r,mav,ans;
}tr[4*N];
inline int clac(int id,int v)
{	
	if(tr[id].l==tr[id].r)return (tr[id].mav>v?f[b[tr[id].l]]:1e12);
	if(tr[id*2+1].mav>v)return min(tr[id].ans,clac(id*2+1,v));
	else return clac(id*2,v);
}
inline void qi(int id)
{
	tr[id].mav=max(tr[id*2].mav,tr[id*2+1].mav);
	tr[id].ans=clac(id*2,tr[id*2+1].mav);
}
void build(int id,int l,int r)
{
	tr[id].l=l;tr[id].r=r;tr[id].ans=1e12;tr[id].mav=-1e12;
	if(l==r)return;int mid=(l+r)>>1;
	build(id*2+1,mid+1,r);build(id*2,l,mid);
	qi(id);
}	
void change(int id,int p)
{
	if(tr[id].l==tr[id].r){tr[id].mav=b[tr[id].l];return;}
	int mid=(tr[id].l+tr[id].r)>>1;
	if(p<=mid)change(id*2,p);
	else change(id*2+1,p);
	qi(id);
}
int ga;
int get(int id,int l,int r)
{
	if(l<=tr[id].l&&r>=tr[id].r)
	{	
		int an=clac(id,ga);
		ga=max(ga,tr[id].mav);
		return an;
	}	
	int mid=(tr[id].l+tr[id].r)>>1;
	if(l>mid)return get(id*2+1,l,r);
	if(r<=mid)return get(id*2,l,r);
	int an1=get(id*2+1,mid+1,r),an2=get(id*2,l,mid);
	return min(an1,an2);
}
signed main()
{	
	cin>>n;
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]),b[a[i]]=i;
	for(int i=1;i<=n;i++)scanf("%lld",&w[i]);
	a[n+1]=b[n+1]=n+1;w[n+1]=0;	
	build(1,0,n+1);
	memset(f,0x3f,sizeof(f));f[0]=0;
	for(int i=1;i<=n+1;i++)
	{
		ga=-1;int s=get(1,0,a[i]);
		f[i]=((s>=1e12)?0:s)+w[i];
		change(1,a[i]);
	}
	cout<<f[n+1]<<endl;
	return 0;
}

Niu Banxian's sister sequence

Similarly, if the discovery is a scheme count, just change the maintenance \ (min \) to and, and everything else is the same
Also, insert the position of 0 at the beginning, otherwise it will be adjusted to death... Don't put the code

summary

A (possibly) more useful model, of course, mainly depends on skilled use

Tags: data structure

Posted on Sun, 31 Oct 2021 18:41:34 -0400 by thebluebus