To_Heart—题解——[UR #19]前进四

感觉这种转换很神仙。没见过。或者是对势能线段树了解不够多?反正很神仙就是了

题目大意

link.

题面有人性化题意/se.

题解

转换一下题意其实就是让你求从后往前每个位置的极长下降子序列长度,带修,板子是 O ( n log ⁡ n 2 ) O(n\log n^2) O(nlogn2) 的!但是数据范围在 1 0 6 10^6 106 最多单 log。

考虑转换,发现一个了不起的性质,就是答案不会超过 n n n

也许你认为这是废话,但并不是。答案是极长下降子序列的长度,每次改变都是减小,而且不会超过 n n n 次改变。为了利用上这个优秀的性质,选择将询问离线后从后往前枚举下标,维护询问。那么修改变成了对一段询问区间取 min,询问变成了对于某个询问单点求 min 值的变化次数 发现这个变换次数刚好可以用势能表示,所以问题用势能线段树即可解决。

这告诉我们什么?答案的改变单调且范围与 n 同阶或者更小的时候,可以考虑利用势能来解释并相对应的提出解决方法。

#include<bits/stdc++.h>
using namespace std;
int read(){
	int x=0;char c=getchar();
	while(c<'0'||c>'9') c=getchar();
	while('0'<=c&&c<='9') x=(x<<1)+(x<<3)+c-'0',c=getchar();
	return x;
}

struct zz{
	int l,r,tag;
	int mx,cx;
};
int n,m;
vector<int> q[1000005];
struct ss{
	int x,y;
};
vector<ss> v[1000005];

struct Tree{
	#define lc p<<1
	#define rc p<<1|1
	zz t[4000005];
	void Push_Down(int p){
		if(!t[p].tag) return ;
		if(t[p].mx<t[lc].mx) t[lc].mx=t[p].mx,t[lc].tag+=t[p].tag;
		if(t[p].mx<t[rc].mx) t[rc].mx=t[p].mx,t[rc].tag+=t[p].tag;
		t[p].tag=0;
	}
	void Push_Up(int p){
		t[p].mx=max(t[lc].mx,t[rc].mx);
		t[p].cx=max(t[lc].cx,t[rc].cx);
		if(t[p].mx!=t[lc].mx) t[p].cx=max(t[p].cx,t[lc].mx);
		if(t[p].mx!=t[rc].mx) t[p].cx=max(t[p].cx,t[rc].mx);
	}
	void Build_Tree(int p,int l,int r){
		t[p].l=l,t[p].r=r,t[p].tag=0;t[p].mx=0x3f3f3f3f,t[p].cx=-0x3f3f3f3f;
		if(l==r) return void();
		int mid=(t[p].l+t[p].r)>>1;
		Build_Tree(lc,l,mid),Build_Tree(rc,mid+1,r); 
		Push_Up(p);
	}
	void Change_Tree(int p,int l,int r,int x){
		if(t[p].mx<=x) return ;
		if(l<=t[p].l&&t[p].r<=r&&t[p].cx<x) return t[p].mx=x,t[p].tag++,void();
		Push_Down(p);
		int mid=(t[p].l+t[p].r)>>1;
		if(l<=mid) Change_Tree(lc,l,r,x);
		if(mid+1<=r) Change_Tree(rc,l,r,x);
		Push_Up(p);
	}
	int Find_Tree(int p,int l){
		if(t[p].l==t[p].r) return t[p].tag;
		Push_Down(p);
		int mid=(t[p].l+t[p].r)>>1;
		if(l<=mid) return Find_Tree(lc,l);
		else return Find_Tree(rc,l);
	}
}T;

int ans[1000005];
int main(){
	cin>>n>>m;
	for(int i=1,x;i<=n;i++){
		x=read();
		v[i].push_back((ss){x,0});
	}
	for(int i=1,x,y,op;i<=m;i++){
		op=read();
		if(op&1){
			x=read(),y=read();
			v[x].push_back((ss){y,i});
		}
		else{
			x=read();
			q[x].push_back(i);
		}	
	}
	T.Build_Tree(1,1,m);
	for(int i=1;i<=n;i++) v[i].push_back((ss){0,m});
	for(int i=n;i>=1;i--){
		for(int j=0;j+1<v[i].size();j++)
			T.Change_Tree(1,v[i][j].y+1,v[i][j+1].y,v[i][j].x);
		for(auto x:q[i]) ans[x]=T.Find_Tree(1,x);
	}
	for(int i=1;i<=m;i++) if(ans[i]) printf("%d\n",ans[i]);
	return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值