析合树学习小记

析合树……怎么说呢,应该是一个排列的一种划分方法,用于处理连续段有关问题。


首先定义连续段:
对于一个排列的一个区间 [ x , y ] [x,y] [x,y],如果把这个区间的数拿出来排序,是连续的若干个数,即 y − x + 1 = m a x ( x . . y ) − m i n ( x . . y ) + 1 y-x+1=max(x..y)-min(x..y)+1 yx+1=max(x..y)min(x..y)+1,则称 [ x , y ] [x,y] [x,y]为一个连续段。


总感觉直接说划分方法有点说不清,再说一些定义:
析合树上的每个点代表的是一个区间,当然它是一棵树。
每个点的子节点的区间互不相交,且并起来是这个点代表的区间。
点分为析点和合点两种:
1.析点:
把这个的子节点重新排列,取任意长度>1的区间,均不是连续段。
2.合点:
……,均是连续段

解释重新排列:
如果一个值域是[1,10]的点的子节点的值域分别为[3,6]、[1,2]、[8,10]、[7,7]
那么重新排列就变成了2,1,4,3


一个重要的结论是:
一个排列一定可以用析合树划分出来,即root代表的就是整个排列。
划分显然不止一种,但有一种析合树划分,即极大的一种(极大不好理解没有关系),是唯一的,且有很多优美的性质。

当我们拿到一个排列时,你想直接知道root是析点还是合点都很难,所以我们要考虑自下而上,可以用增量法建树。

假设现在已经搞定了[1…i]的析和森林,我们把每个析合树的root拿出来放到一个栈里(代表区间后的在栈顶)

现在,要加入第i+1个位置,先建一个析点就表示这个位置,设为x。

设要加入的点是x,栈顶的点是y。

那么一共有三种情况:

  1. y是一个合点,x能成为y新的儿子,拿y递归加入。
  2. 1不行时,新生成一个点z,作为y和x共有合点父亲,拿z递归加入。
  3. 1、2都不行时,新生成一个点z,作为x和栈顶的若干点(尽量少)的共有析点父亲,拿z递归加入。

当以上三种情况都不行时,说明不能再合并什么的了,直接把x加入栈顶。

不难得到析合树的点数是 O ( n ) O(n) O(n)的。

可以知道1、2操作的复杂度就对应树的点数,但是3操作最坏情况需要遍历整棵单调栈。

例如:当root是一个儿子个数为n的析点时,复杂度就变成了 O ( n 2 ) O(n^2) O(n2)


现在需要考虑用奇技淫巧去优化这个。

对一个区间[x…y],求出 a [ i ] ∈ [ m i n . . m a x ] a[i]∈[min..max] a[i][min..max]的i的最小值和最大值,不妨设为[l…r]

显然当r>y时,右端点不变,x往左的左段点都不可能是连续段。

那么对单调栈里的每个点,维护一个fail指针,它在加入单调栈前,不是要做3操作吗?失败了才被加入单调栈,fail记录的就是倒着合并时第一次r>y的时候。

假设现在有一个新的点要做3操作,先和栈顶判一下,如果不行,就跳到栈顶的fail去,直到跳到同样的r>y时 或者 是析点了。

很容易证明 可能的析点左分界点 一定在fail链上
且这样做的复杂度是 O ( n ) O(n) O(n)的,因为一条fail链被跳过一次后就一定不会再被跳。

至于[l,r]可以处理个ST表,也可以求相邻的然后就合并,复杂度一般要一个log,用毛子算法就可以做到 O ( n ) O(n) O(n)


个人觉得这个东西并不是非常好写,但是利用模块化思想会清晰很多,大概就是用个struct来表示各种类型,再写几个函数来merge、判断是不是连续段

裸题:
JZOJ6202. c

#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, B = y; i <= B; i ++)
#define ff(i, x, y) for(int i = x, B = y; i <  B; i ++)
#define fd(i, x, y) for(int i = x, B = y; i >= B; i --)
#define ll long long
#define pp printf
#define hh pp("\n")
#define max(a, b) ((a) > (b) ? (a) : (b))
using namespace std;

void cmax(int &x, int y) { x < y ? x = y : 0;}
void cmin(int &x, int y) { x > y ? x = y : 0;}

const int N = 2e5 + 5;

int n, a[N], na[N], m, x, y;

int t0[N * 4], t1[N * 4], pl, pr, px0, px1;
#define i0 i + i
#define i1 i + i + 1
void bt(int i, int x, int y) {
	if(x == y) { t0[i] = t1[i] = na[x]; return;}
	int m = x + y >> 1;
	bt(i0, x, m); bt(i1, m + 1, y);
	t0[i] = min(t0[i0], t0[i1]);
	t1[i] = max(t1[i0], t1[i1]);
}
void ft(int i, int x, int y) {
	if(y < pl || x > pr) return;
	if(x >= pl && y <= pr) {
		cmin(px0, t0[i]);
		cmax(px1, t1[i]);
		return;
	}
	int m = x + y >> 1;
	ft(i0, x, m); ft(i1, m + 1, y);
}

struct P {
	int x, y;
	P(){ x = 100000, y = 0;}
	P(int _x, int _y) {x = _x, y = _y;}
} c[N];

void bin(P &a, P b) {
	cmin(a.x, b.x);
	cmax(a.y, b.y);
}

struct nod {
	int ty, ls;
	P a, b, c, d;
	nod() {
		a = b = c = d = P();
	}
} b[N]; int b0;

nod bin(nod a, nod b) {
	bin(a.a, b.a);
	bin(a.b, b.b);
	a.d = a.c; bin(a.d, b.d);
	bin(a.c, b.c);
	return a;
}

int len(nod a) { return a.a.y - a.a.x + 1;}
int vlen(nod a) { return a.b.y - a.b.x + 1;}
int pd(nod a) {
	return len(a) == vlen(a);
}

int z[N], z0;
int f[18][N];

struct Fail {
	int x;
	nod a;
} d[N];

int add(int x) {
	if(!z0) {
		z[++ z0] = x;
		return 0;
	}
	int y = z[z0];
	if(!b[y].ty && pd(bin(b[b[y].ls], b[x]))) {
		b[y] = bin(b[y], b[x]);
		b[y].ls = x;
		f[0][x] = y;
		z0 --;
		return y;
	}
	if(pd(bin(b[y], b[x]))) {
		b[++ b0] = bin(b[y], b[x]);
		b[b0].ls = x;
		b[b0].ty = 0;
		f[0][y] = f[0][x] = b0;
		z0 --;
		return b0;
	}
	int t = z0; y = z[t];
	nod e = bin(b[y], b[x]);
	while(e.d.y <= b[x].a.y && !pd(e)) {
		y = z[t];
		e = bin(d[t].a, e);
		t = d[t].x;
	}
	if(pd(e)) {
		b0 ++;
		z[++ z0] = x;
		fo(i, t, z0) {
			f[0][z[i]] = b0;
			b[b0] = bin(b[b0], b[z[i]]);
		}
		b[b0].ty = 1;
		b[b0].ls = x;
		z0 = t - 1; return b0;
	} else {
		z[++ z0] = x;
		d[z0].x = t;
		d[z0].a = e;
		return 0;
	}
}

int q[N], r[N], dep[N];

int lca(int x, int y) {
	if(dep[x] < dep[y]) swap(x, y);
	fd(i, 17, 0) if(dep[f[i][x]] >= dep[y]) x = f[i][x];
	if(x == y) return x;
	fd(i, 17, 0) if(f[i][x] != f[i][y]) x = f[i][x], y = f[i][y];
	return f[0][x];
}
int xia(int x, int y) {
	fd(i, 17, 0) if(dep[f[i][y]] > dep[x]) y = f[i][y];
	return y;
}

int ed[N];

int main() {
	freopen("c.in", "r", stdin);
	freopen("c.out", "w", stdout);
	scanf("%d", &n);
	fo(i, 1, n)	scanf("%d", &a[i]), na[a[i]] = i;
	bt(1, 1, n);
	fo(i, 1, n - 1) {
		pl = a[i], pr = a[i + 1];
		if(pl > pr) swap(pl, pr);
		px0 = n; px1 = 0;
		ft(1, 1, n);
		c[i].x = px0; c[i].y = px1;
	}
	fo(i, 1, n) {
		b[++ b0].ty = 1;
		b[b0].a = P(i, i);
		b[b0].b = P(a[i], a[i]);
		b[b0].c = c[i];
		b[b0].d = P();
		ed[i] = b0;
		int x = b0;
		do x = add(x); while(x);
	}
	fo(i, 1, b0) r[f[0][i]] ++;
	fo(i, 1, b0) if(!r[i]) q[++ q[0]] = i;
	for(int i = 1; i <= q[0]; i ++) {
		int x = q[i];
		if(f[0][x] && !(-- r[f[0][x]])) q[++ q[0]] = f[0][x];
	}
	fd(i, q[0], 1) dep[q[i]] = dep[f[0][q[i]]] + 1;
	fo(j, 1, 17) fo(i, 1, b0) {
		f[j][i] = f[j - 1][f[j - 1][i]];
	}
	scanf("%d", &m);
	fo(i, 1, m) {
		scanf("%d %d", &x, &y);
		int z = lca(ed[x], ed[y]);
		if(b[z].ty) {
			pp("%d %d\n", b[z].a.x, b[z].a.y);
		} else {
			x = xia(z, ed[x]), y = xia(z, ed[y]);
			nod w = bin(b[x], b[y]);
			pp("%d %d\n", w.a.x, w.a.y);
		}
	}
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Protobuf是一种高效的序列化协议,可以用于数据交换和数据存储。它的主要优势是大小小,速度快,可扩展性强。下面是使用Protobuf的一些小记: 1. 定义消息格式 首先,需要定义消息格式,以便Protobuf可以将数据序列化和反序列化。消息格式定义在.proto文件中,使用protobuf语言编写。例如,下面是一个简单的消息格式定义: ``` syntax = "proto3"; message Person { string name = 1; int32 age = 2; } ``` 这个消息格式定义了一个名为Person的消息,包含两个字段:name和age。 2. 生成代码 一旦消息格式定义好,就可以使用Protobuf编译器生成代码。编译器将根据消息格式定义生成相应的代码,包括消息类、序列化和反序列化方法等。可以使用以下命令生成代码: ``` protoc --java_out=. message.proto ``` 这将生成一个名为message.pb.java的Java类,该类包含Person消息的定义以及相关方法。 3. 序列化和反序列化 一旦生成了代码,就可以使用Protobuf序列化和反序列化数据。例如,下面是一个示例代码,将一个Person对象序列化为字节数组,并将其反序列化为另一个Person对象: ``` Person person = Person.newBuilder() .setName("Alice") .setAge(25) .build(); byte[] bytes = person.toByteArray(); Person deserializedPerson = Person.parseFrom(bytes); ``` 这个示例代码创建了一个Person对象,将其序列化为字节数组,然后将其反序列化为另一个Person对象。在这个过程中,Protobuf使用生成的代码执行序列化和反序列化操作。 以上是使用Protobuf的一些基本步骤和注意事项,希望对你有所帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值