算重学(1) 函数式编程&DS

引入

定义定义定义!

感觉理解分治的时候挺好用的,也就是我常说的推锅下去。

函数式线段树(主席树)

函数式平衡树 (fhq_treap)

以及若干东西,你都可以定义下去。

本质上是一个映射,对于你输入的东西映射,再通过定义去得到最终结果。



https://max.book118.com/html/2016/0304/36834109.shtm

fhq-treap

考虑一个东西

fhq_treap 需要满足 \(val_1\) 满足 BST 性质,\(val_2\) 满足 heap 性质,你可以认为 \(val_2\) 是为了让 fhq_treap 平衡才加进去的一个随机权值。

考虑定义若干函数:

pair<int,int> split(x,k) 表示将 \(x\) 这棵树分裂成两棵 treap,满足第一棵 treap 的 \(val_1\) 的最大值小于等于 \(k\),第二棵 treap 的最小值大于 \(k\)

考虑我仅仅只需要满足当前该咋做,接着推锅下去即可。

if(val1(x)<=k) {
	a=x; auto qwq=split(x.rs,k);
	a.rs=qwq.first;
	b=qwq.second;
} else { //则把 x 的 rs 都给 b,然后考虑 ls 即可。 
	b=x; auto qwq=split(x.ls,k);
	a=qwq.first;
	b.ls=qwq.second;
}
return mp(a,b); 

注意看我每一步只做了什么?当前是咋样的,通过子问题得到的结果来保证当前的正确性。因为只会跑 O(树高) 次,然后你 fhq_treap 保持平衡,所以复杂度就是对的。

注意,每次 split 完只需要 push_up 形态在当前改变的

merge(x,y) 合并 x,y 两棵树。需要注意,建议你把所有的树都看成外向树,也就是你实际上合并的是子树 x,子树 y,它可能原先所在的树并不以它为根。不过这没关系,因为我们定义就是以它为根,既然最开始的时候满足,那么我们只需要接下来每一步都满足即可。

我们限定 x,y 满足 \(\max val_1 T(x)<\min val_1 T(y)\),所以你合并的时候 \(val_1\) 的性质是很好满足的,所以你只需要满足 heap 的性质。

若我们钦定合并完是以当前的 y 为根(根据 \(val_2\) 大小钦定,即满足堆的性质),因为你要么 x 为根,要么 y 为根,因为我们不带旋转操作,下面的点没办法旋上来。

push_down(y); push_down(x);
y.ls=merge(x,y.ls); 
return y;

复杂度显然还是树高次。

因为你若以 \(y\) 为根,显然其 rs 都是直接保留的,因为你 \(x\) 不可能能插到其 rs 的。那么你只需要考虑 ls 对应的树,显然为 x,y.ls 合并起来的,注意顺序不能颠倒!需要满足定义!

#include <bits/stdc++.h>
#define ll long long
#define pb push_back
#define mp make_pair
using namespace std;
const int N=(int)(1e5+5);
mt19937 RAND(time(0)); 
struct Tr {
	int ls,rs,rad,sz,val,tag;
}t[N];
int rt,tot,n,m;

int nd(int v) {
	++tot;
	t[tot].val=v; t[tot].ls=t[tot].rs=t[tot].tag=0; t[tot].rad=RAND();
	t[tot].sz=1; 
	return tot;
}

void push_up(int x) {
	t[x].sz=t[t[x].ls].sz+t[t[x].rs].sz+1;
}

void push_down(int x) {
	if(!t[x].tag) return ;
	t[t[x].ls].tag^=1; t[t[x].rs].tag^=1;
	t[x].tag=0;
	swap(t[x].ls,t[x].rs);
	
}

pair<int,int> split(int x,int sz) {
	if(!x) return mp(0,0);
	push_down(x);
	int a,b;
	if(t[t[x].ls].sz+1<=sz) {
		a=x; 
		auto qwq=split(t[x].rs,sz-t[t[x].ls].sz-1);
		t[a].rs=qwq.first; b=qwq.second;
//		push_up(a); push_up(b);
	} else {
		b=x;
		auto qwq=split(t[x].ls,sz);
		a=qwq.first; t[b].ls=qwq.second;
//		push_up(a); push_up(b);
	}
//	push_up(a); push_up(b); 
	push_up(x);
	return mp(a,b);
}

int merge(int x,int y) {
	if(!x||!y) return x+y;
	push_down(x); push_down(y);
	if(t[x].rad<t[y].rad) {
		t[x].rs=merge(t[x].rs,y);
		push_up(x);
		return x;
	} else {
		t[y].ls=merge(x,t[y].ls);
		push_up(y);
		return y;
	}
}

void pr(int x) {
	push_down(x); 
	if(t[x].ls) pr(t[x].ls);
	cout<<t[x].val<<' ';
	if(t[x].rs) pr(t[x].rs);
}

signed main() {
	cin.tie(0); ios::sync_with_stdio(false);
	cin>>n>>m;
	for(int i=1;i<=n;i++) rt=merge(rt,nd(i));
	while(m--) {
		int l,r; cin>>l>>r;
		auto qwq=split(rt,r);
		int k1=qwq.first,k2=0,k3=qwq.second;
		qwq=split(k1,l-1);
		k1=qwq.first; k2=qwq.second;
		t[k2].tag^=1;
		rt=merge(k1,k2);
		rt=merge(rt,k3);
	}
	pr(rt);
	return 0;
}

可持久化

然后可持久化是很好做的,具体的,你只需要根据定义来做即可。即类主席树一样定义若干个版本的树,然后你显然是要根据定义来继承旧版本信息的。

你就直接定义 split 为分裂出 2 棵新 treap,而不更改原先节点的形态,merge 也是合并为新 treap,不更改原先 2 棵树的本身结构。

因此,你每次都要建新点来保证原先形态不被改变。即倘若有操作需要改变到原先的形态,你都需要复制原先的点生成一个新点,再在新点上更改形态,因为你可持久化后是认子不认父的,一个点可能有多个父亲,你要是贸然修改可能其他树的结构信息就被破坏掉了。

因为都是外向边,所以认子不认父,一个点可以有多个父亲,这也是为啥复杂度正确。

#include <bits/stdc++.h>
#define int long long
#define pb push_back
#define mp make_pair
using namespace std;
const int N=(int)(2e5+5);
struct Tr {
	int ls,rs,val,rad,sz,sum,tag;
}t[N*100];
mt19937 RAND(time(0));
int m,rt[N],tot;

int nd(int v) {
	++tot;
	t[tot].ls=t[tot].rs=t[tot].tag=0;
	t[tot].val=t[tot].sum=v;
	t[tot].sz=1;
	t[tot].rad=RAND();
	return tot;
}

int cpy(int x) {
	++tot; t[tot]=t[x];
	return tot;
}

void push_down(int x) {
	if(!t[x].tag) return ;
	if(t[x].ls) t[x].ls=cpy(t[x].ls);
	if(t[x].rs) t[x].rs=cpy(t[x].rs);
	swap(t[x].ls,t[x].rs);
	if(t[x].ls) t[t[x].ls].tag^=1;
	if(t[x].rs) t[t[x].rs].tag^=1;
	t[x].tag=0;
}

void push_up(int x) {
	t[x].sz=t[t[x].ls].sz+t[t[x].rs].sz+1;
	t[x].sum=t[t[x].ls].sum+t[t[x].rs].sum+t[x].val;
}

pair<int,int> split(int x,int k) {
	if(!x) return mp(0,0);
	push_down(x);
	int a,b;
	if(t[t[x].ls].sz+1<=k) {
		a=++tot; t[a]=t[x];
		auto qwq=split(t[x].rs,k-t[t[x].ls].sz-1);
		t[a].rs=qwq.first; b=qwq.second;
		push_up(a);
	} else {
		b=++tot; t[b]=t[x];
		auto qwq=split(t[x].ls,k);
		a=qwq.first; t[b].ls=qwq.second;
		push_up(b);
	}
	return mp(a,b);
}

int merge(int x,int y) {
	if(!x||!y) return x+y;
	push_down(x); push_down(y);
	int p=++tot;
	if(t[x].rad<t[y].rad) {
		t[p]=t[x];
		t[p].rs=merge(t[x].rs,y);
	} else {
		t[p]=t[y];
		t[p].ls=merge(x,t[y].ls);
	}
	push_up(p);
	return p;
}

void pr(int x) {
	push_down(x);
	if(t[x].ls) pr(t[x].ls);
//	cout<<x<<" "<<t[x].val<<' ';
	if(t[x].rs) pr(t[x].rs);
}

signed main() {
	cin.tie(0); ios::sync_with_stdio(false);
	cin>>m;
	int ans=0;
	for(int i=1;i<=m;i++) {
		int pre,op; cin>>pre>>op;
		if(op==1) {
			int p,x; cin>>p>>x;
			p^=ans; x^=ans;
			auto qwq=split(rt[pre],p);
			int k1=qwq.first,k2=qwq.second;
			rt[i]=merge(k1,nd(x));
			rt[i]=merge(rt[i],k2);
		} else if(op==2) {
			int p; cin>>p;
			p^=ans;
			auto qwq=split(rt[pre],p-1);
			int k1=qwq.first,k2=0,k3=qwq.second;
			qwq=split(k3,1);
			k2=qwq.first; k3=qwq.second;
			rt[i]=merge(k1,k3);
		} else if(op==3) {
			int l,r; cin>>l>>r;
			l^=ans; r^=ans;
			auto qwq=split(rt[pre],r);
			int k1=qwq.first,k2=0,k3=qwq.second;
			qwq=split(k1,l-1);
			k1=qwq.first; k2=qwq.second;
			t[k2].tag^=1;
			rt[i]=merge(k1,merge(k2,k3));
		} else {
			int l,r; cin>>l>>r;
			l^=ans; r^=ans;
			auto qwq=split(rt[pre],r);
			int k1=qwq.first,k2=0,k3=qwq.second;
			qwq=split(k1,l-1);
			k1=qwq.first; k2=qwq.second;
			ans=t[k2].sum;
			cout<<ans<<'\n';
			rt[i]=merge(k1,merge(k2,k3));
		}
//		cout<<": "<<i<<'\n';
//		pr(rt[i]);
//		cout<<'\n';
	}
	return 0;
} 

posted @ 2022-12-16 12:22  FxorG  阅读(41)  评论(0编辑  收藏  举报