洛谷网课第三节

单调栈:

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

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

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

例题: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;
}

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

倍增:

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

table[p][i]=table[p][i-1]+table[p+2^{i-1}][i-1]

其中table[p][i] 表示从位置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;
}
发布了171 篇原创文章 · 获赞 4 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/FrostMonarch/article/details/104022031