@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 值本身,而存储平衡树对应结点指针会更好维护。