@bzoj - 3600@ 没有人的算术


@description@

(简化版题意)我们定义一种新的“数”:要么是 0;要么是一个“数”对(这里的“数”指我们新定义的数) (l,r), 其中l和r也是“数”。

每一个“数”如果不是0,其一定可以一直拆分直至不可拆分。

“数” 的大小关系定义: 0最小,其余“数”按照“数”对第一项为第一关键字,第二项为第二关键字。

维护一个由n个这样的“数”组成的序列,初始每个位置都是0。支持两种操作,将a[k]修改为(a[l],a[r]),或询问区间最小值。操作总数为m。

原题传送门。

@solution@

基本思路很简单:给 “数” 一个动态维护的 hash 值,使得 hash 值的大小比较等价于 “数” 的大小比较。再用线段树维护 hash 值,单点修改 + 区间 min 即可。

使用平衡树可以维护 “数” 的偏序集(为了方便,我们不把单个 0 放入平衡树),每次新创造的 “数” 可以利用 hash 值 O(nlogn) 插入平衡树中。

那么这个 hash 值怎么选取呢?比较直观的选取方法是直接选结点在平衡树的 rank,不过发现不能很好地维护。

另一种方法是选取根到当前结点的路径代表的 01 字符串:往左子树为 0,往右子树为 1。如果在字符串末尾加字符 1,两个字符串的字典序大小关系就可以表示 “数” 的大小关系(有些类似于 trie)。
如果选取的是重量平衡树(如带旋转的 treap),就可以在 O(nlog^2n) 维护出这种 hash 值。

要是能将字符串压成一个数,就可以去掉一个 log。这也就是网上的大部分题解的做法:我们将 hash 值改成一个实数。
根分配权值区间 (0, 1);假如一个点分配权值区间 (l, r),则该点 hash 值为 (l + r) / 2,左子树分配 (l, m),右子树分配 (m, r)。
因为树高 O(log),所以精度不会有问题。

@accepted code@

#include <cmath>
#include <stack>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;

typedef unsigned int ui;
#define mp make_pair
#define fi first
#define se second

const double EPS = 1E-9;
const int MAXN = 100000;
const int MAXM = 500000;

struct treap{
	struct node{
		ui pri; pair<node*, node*> val;
		double f;
		node *ch[2];
	}pl[MAXM + 5], *ncnt, *NIL, *root;
	typedef pair<node*, node*> Dnode;
	int cmp(Dnode a, Dnode b) {
		if( a.fi->f == b.fi->f && a.se->f == b.se->f )
			return 0;
		if( a.fi->f == b.fi->f )
			return a.se->f < b.se->f ? -1 : 1;
		else return a.fi->f < b.fi->f ? -1 : 1;
	}
	treap() {
		ncnt = NIL = pl;
		NIL->f = 0, NIL->ch[0] = NIL->ch[1] = NIL;
		root = newnode(mp(NIL, NIL)), root->f = 0.5;
	}
	ui get_rand() {return ui(rand() << 16 | rand());}
	node *newnode(Dnode k) {
		node *p = (++ncnt);
		p->val = k, p->ch[0] = p->ch[1] = NIL, p->pri = get_rand();
		return p;
	}
	void rotate(node *y, node *x) {
		int d = (y->ch[1] == x);
		y->ch[d] = x->ch[!d], x->ch[!d] = y;
	}
	double le, ri;
	node *find(double l, double r, node *&rt, Dnode p) {
		if( rt == NIL ) {
			rt = newnode(p), le = l, ri = r;
			return rt;
		}
		else if( cmp(p, rt->val) == 0 )
			return rt;
		else {
			double m = (l + r) / 2;
			node *ret = (cmp(p, rt->val) < 0 ? find(l, m, rt->ch[0], p) : find(m, r, rt->ch[1], p));
			if( ret->pri > rt->pri )
				rotate(rt, ret), rt = ret, le = l, ri = r;
			return ret;
		}
	}
	void reget(double l, double r, node *x) {
		if( x == NIL ) return ;
		x->f = (l + r) / 2;
		reget(l, x->f, x->ch[0]), reget(x->f, r, x->ch[1]);
	}
	node *find(Dnode p) {
		le = ri = -1;
		node *ret = find(0, 1, root, p);
		if( le != -1 && ri != -1 ) reget(le, ri, ret);
		return ret;
	}
}T1;

struct type{
	treap::node *a; int b; type() {}
	type(treap::node *_a, int _b) : a(_a), b(_b) {}
	friend bool operator < (type a, type b) {
		return a.a->f == b.a->f ? (a.b > b.b) : a.a->f < b.a->f;
	}
};

struct segtree{
	#define lch (x << 1)
	#define rch (x << 1 | 1)
	
	int le[4*MAXN + 5], ri[4*MAXN + 5]; type mx[4*MAXN + 5];
	
	void pushup(int x) {mx[x] = max(mx[lch], mx[rch]);}
	void build(int x, int l, int r) {
		le[x] = l, ri[x] = r;
		if( l == r ) {
			mx[x] = type(T1.NIL, l);
			return ;
		}
		int m = (l + r) >> 1;
		build(lch, l, m), build(rch, m + 1, r);
		pushup(x);
	}
	void modify(int x, int p, treap::node *k) {
		if( le[x] == ri[x] ) {
			mx[x] = type(k, p);
			return ;
		}
		int m = (le[x] + ri[x]) >> 1;
		if( p <= m ) modify(lch, p, k);
		else modify(rch, p, k);
		pushup(x);
	}
	type query(int x, int l, int r) {
		if( l > ri[x] || r < le[x] )
			return type(T1.NIL, MAXN + 5);
		if( l <= le[x] && ri[x] <= r )
			return mx[x];
		return max(query(lch, l, r), query(rch, l, r));
	}
}T2;

int n, m;

int main() {
	scanf("%d%d", &n, &m);
	srand(20041112), T2.build(1, 1, n);
	for(int i=1;i<=m;i++) {
		char op[2]; scanf("%s", op);
		if( op[0] == 'C' ) {
			int l, r, k; scanf("%d%d%d", &l, &r, &k);
			treap::node *L = T2.query(1, l, l).a, *R = T2.query(1, r, r).a;
			treap::node *K = T1.find(mp(L, R));
			T2.modify(1, k, K);
		}
		else {
			int l, r; scanf("%d%d", &l, &r);
			printf("%d\n", T2.query(1, l, r).b);
		}
	}
}
/*
5 10
C 1 1 1
C 2 1 2
Q 1 2
C 4 4 4
C 5 5 5
Q 4 5
Q 3 3
C 4 2 3
C 4 4 4
Q 3 4

2
4
3
3
*/

@details@

一个小细节:线段树里面不存储 hash 值本身,而存储平衡树对应结点指针会更好维护。

posted @ 2020-03-05 16:17  Tiw_Air_OAO  阅读(129)  评论(0编辑  收藏  举报