http://www.lydsy.com/JudgeOnline/problem.php?id=1588
splay tree 中的一些操作的说明:
sc函数:
inline void sc(int y,int x,int p) {
如果p==0,则y是x的父亲,x是y的左儿子;
如果p==1,则y是x的父亲,x是y的右儿子;
}
rot函数:
inline void rot(int x) {
y是x的父亲节点;
如果x是y的左儿子,则w=0;如果x是y的右儿子,则w=1;
设p是y的父亲;
对x,y,p进行zig或zag操作;
}
效果:每次rot(x),x向根靠近一单位深度
splay函数:
void splay(int x,int rt) {
不断zig、zag,使得最后x升到根的位置;
}
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; #define inf (1<<29) const int maxn = 100010; int sum , splaysz; struct node { int p,chd[2],v; node (int P=0,int val=0) { chd[0]=chd[1]=0; p = P; v= val; } }tree[maxn]; inline void sc(int y,int x,int p) { tree[y].chd[p]=x;tree[x].p=y; } inline void rot(int x) { int y = tree[x].p; int w = tree[y].chd[1] == x; sc(tree[y].p,x,tree[tree[y].p].chd[1]==y); sc(y,tree[x].chd[w^1],w); sc(x,y,w^1); } void splay(int x,int rt) { while(tree[x].p!=rt) { int y = tree[x].p; int w = tree[y].chd[1]==x; if(tree[y].p != rt && tree[tree[y].p].chd[w]==y) rot(y); rot(x); } } int insert(int val) { if(tree[0].chd[0]==0) { tree[++splaysz] = node(0,val); tree[0].chd[0] = splaysz; return 1; } int now = tree[0].chd[0]; while(1) { if(val == tree[now].v) { return 0; } int w = val > tree[now].v; if(tree[now].chd[w]) now = tree[now].chd[w]; else { tree[++splaysz] = node(now,val); tree[now].chd[w]= splaysz; splay(splaysz,0); return 1; } } } int get_pre() { int y = tree[0].chd[0]; if(tree[y].chd[0] == 0) return -inf; y = tree[y].chd[0]; while(tree[y].chd[1]) { y = tree[y].chd[1]; } return tree[y].v; } int get_next() { int y = tree[0].chd[0]; if(tree[y].chd[1] == 0) return inf; y = tree[y].chd[1]; while(tree[y].chd[0]) { y = tree[y].chd[0]; } return tree[y].v; } int main() { int n; while(~scanf("%d" , &n)) { sum = splaysz = 0; for(int i=1;i<=n;i++) { int num; if(scanf("%d",&num) == EOF) num = 0; if(i == 1) { sum += num; insert(num); continue; } if(insert(num) == 0) continue; int a = get_next() - num; int b = num - get_pre(); sum += min(a , b); } printf("%d\n" , sum); } return 0; }
这份代码在bzoj上面跑了1076ms,可能是因为将zig,zag操作归约到了一起,以前的代码跑了152ms。
以前的代码:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; #define inf (1<<29) const int maxn = 100100; int pre[maxn],key[maxn],ch[maxn][2],root,tot;//分别表示父结点,键值,左右孩子(0为左孩子,1为右孩子),根结点,结点数量 int n; //新建一个结点 void new_node(int &r,int fa,int k) { r = ++tot; pre[r] = fa; key[r] = k; ch[r][0] = ch[r][1] = 0; //左右孩子为空 } //旋转,kind为1为右旋,kind为0为左旋 void rot(int x,int kind) { int y = pre[x]; //类似SBT,要把其中一个分支先给父节点 ch[y][!kind] = ch[x][kind]; pre[ch[x][kind]] = y; //如果父节点不是根结点,则要和父节点的父节点连接起来 if(pre[y]) ch[pre[y]][ch[pre[y]][1]==y] = x; pre[x] = pre[y]; ch[x][kind] = y; pre[y] = x; } //Splay调整,将根为r的子树调整为goal void splay(int r,int goal) { while(pre[r] != goal) { //父节点即是目标位置,goal为0表示,父节点就是根结点 if(pre[pre[r]] == goal) rot(r,ch[pre[r]][0]==r); else { int y = pre[r]; int kind = ch[pre[y]][0] == y; //两个方向不同,则先左旋再右旋 if(ch[y][kind] == r) { rot(r , !kind); rot(r , kind); } //两个方向相同,相同方向连续两次 else { rot(y , kind); rot(r , kind); } } } //更新根结点 if(goal == 0) root = r; } int insert(int k) { int r = root; while(ch[r][key[r]<k]) { //不重复插入 if(key[r] == k) { splay(r , 0); return 0; } r = ch[r][key[r]<k]; } new_node(ch[r][k>key[r]] , r , k); splay(ch[r][k>key[r]] , 0); return 1; } //找前驱,即左子树的最右结点 int get_pre(int x) { int tmp = ch[x][0]; if(tmp == 0) return inf; while(ch[tmp][1]) tmp = ch[tmp][1]; return key[x] - key[tmp]; } //找后继,即右子树的最左结点 int get_next(int x) { int tmp = ch[x][1]; if(tmp == 0) return inf; while(ch[tmp][0]) tmp = ch[tmp][0]; return key[tmp] - key[x]; } int main() { while(~scanf("%d" , &n)) { root = tot = 0; int ans = 0; for(int i=1;i<=n;i++) { int num; if(scanf("%d",&num)==EOF) num = 0; if(i == 1) { ans += num; new_node(root , 0 , num); continue; } if(insert(num) == 0) continue; int a = get_next(root); int b = get_pre(root); ans += min(a , b); } printf("%d\n" , ans); } return 0; }