LA 5031 Graph and Queries —— Treap名次树

  离线做法,逆序执行操作,那么原本的删除边的操作变为加入边的操作,用名次树维护每一个连通分量的名次,加边操作即是连通分量合并操作,每次将结点数小的子树向结点数大的子树合并,那么单次合并复杂度O(n1logn2),由于合并之后原本结点数少的子树结点数至少翻倍,所以每个结点最多被插入 logn 次,故总时间复杂度为

O(n log2n)  。

注意细节处理,代码如下:

  1 #include <cstdio>
  2 #include <cstdlib>
  3 #include <cstring>
  4 #include <algorithm>
  5 #include <vector>
  6
  7 using namespace std;
  8
  9
 10 struct Node {
 11     Node *ch[2];
 12     int r;
 13     int v;
 14     int s;
 15     Node(int vv): v(vv) {
 16         s = 1;
 17         ch[0] = ch[1] = NULL;
 18         r = rand();
 19     }
 20     int cmp(int x) const {
 21         if(x == v) return -1;
 22         return x < v ? 0 : 1;
 23     }
 24     void maintain() {
 25         s = 1;
 26         if(ch[0] != NULL) s += ch[0]->s;
 27         if(ch[1] != NULL) s += ch[1]->s;
 28     }
 29 };
 30
 31 void rotate(Node* &o, int d) {
 32     Node* k = o->ch[d^1]; o->ch[d^1] = k->ch[d]; k->ch[d] = o;
 33     o->maintain(); k->maintain(); o = k;
 34 }
 35
 36 void insert(Node* &o, int x) {
 37     if(o == NULL) o = new Node(x);
 38     else {
 39         int d = x < o->v ? 0 : 1;
 40         insert(o->ch[d], x);
 41         if(o->ch[d]->r > o->r) rotate(o, d^1);
 42     }
 43     o->maintain();
 44 }
 45 void remove(Node* &o, int x) {
 46     int d = o->cmp(x);
 47     Node* u = o;
 48     if(d == -1) {
 49         if(o->ch[0] != NULL && o->ch[1] != NULL){
 50             int d2 = o->ch[0]->r > o->ch[1]->r ? 1 : 0;
 51             rotate(o, d2);
 52             remove(o->ch[d2], x);
 53         }
 54         else {
 55             if(o->ch[0] == NULL) o = o->ch[1]; else o = o->ch[0];
 56             delete u;
 57         }
 58     }
 59     else remove(o->ch[d], x);
 60     if(o != NULL) o->maintain();
 61 }
 62
 63 int kth(Node* o, int k) {
 64     if(o == NULL || k > o->s || k <= 0) return 0;
 65     int s = o->ch[1] == NULL ? 0 : o->ch[1]->s;
 66     if(k == s+1) return o->v;
 67     else if(k <= s) return kth(o->ch[1], k);
 68     else return kth(o->ch[0], k-s-1);
 69 }
 70
 71
 72 struct cmd {
 73     char type;
 74     int x, p;
 75 };
 76
 77 vector<cmd> cmds;
 78
 79 const int maxn = 2e4 + 10;
 80 const int maxm = 6e4 + 10;
 81 int n, m;
 82 int weight[maxn], from[maxm], to[maxm], removed[maxm];
 83
 84 int pa[maxn];
 85 int findpa(int x) {return x == pa[x] ? x : pa[x] = findpa(pa[x]);}
 86 long long sum;
 87 int cnt;
 88 Node* root[maxn];
 89
 90 void mergetreeto(Node* &ser, Node* &to) {
 91     if(ser->ch[0] != NULL) mergetreeto(ser->ch[0], to);
 92     if(ser->ch[1] != NULL) mergetreeto(ser->ch[1], to);
 93     insert(to, ser->v);
 94     delete ser;
 95     ser = NULL;
 96 }
 97
 98 void removetree(Node *&ser) {
 99     if(ser == NULL) return;
100     if(ser->ch[0] != NULL) removetree(ser->ch[0]);
101     if(ser->ch[1] != NULL) removetree(ser->ch[1]);
102     delete ser;
103     ser = NULL;
104 }
105
106 void add_edge(int id) {
107     int x = findpa(pa[from[id]]);
108     int y = findpa(pa[to[id]]);
109     if(x != y) {
110         if(root[x]->s < root[y]->s) mergetreeto(root[x], root[y]), pa[x] = y;
111         else mergetreeto(root[y], root[x]), pa[y] = x;
112     }
113 }
114
115 void querycnt(int x, int k) {
116     cnt++;
117     sum += kth(root[findpa(x)], k);
118 }
119
120 void change_w(int x, int v) {
121     int u = findpa(pa[x]);
122     remove(root[u], weight[x]);
123     insert(root[u], v);
124     weight[x] = v;
125 }
126
127 void init() {
128     cmds.clear();
129     cnt = 0;
130     sum = 0;
131     memset(removed, 0, sizeof removed);
132     for(int i = 1; i < n; i++) removetree(root[i]);
133 }
134 int main() {
135     int kase = 0;
136     while(scanf("%d%d", &n, &m) == 2 && n) {
137         for(int i = 1; i <= n; i++) scanf("%d", &weight[i]);
138         for(int i = 1; i <= m; i++) {
139             int u, v;
140             scanf("%d%d", &u, &v);
141             from[i] = u;
142             to[i] = v;
143         }
144         init();
145         while(1) {
146             getchar();
147             char ch;
148             scanf("%c", &ch);
149             cmd C;
150             C.type = ch;
151             C.x = C.p = 0;
152             if(ch == ‘E‘) break;
153             scanf("%d", &C.x);
154             if(ch == ‘D‘) removed[C.x] = 1;
155             if(ch == ‘Q‘) scanf("%d", &C.p);
156             if(ch == ‘C‘) {
157                 scanf("%d", &C.p);
158                 swap(C.p, weight[C.x]);
159             }
160             cmds.push_back(C);
161         }
162         for(int i = 1; i <= n; i++) {
163             pa[i] = i;
164             root[i] = new Node(weight[i]);
165         }
166         for(int i = 1; i <= m; i++)
167             if(!removed[i]) add_edge(i);
168
169         for(int i = cmds.size()-1; i >= 0; i--) {
170             cmd C = cmds[i];
171             if(C.type == ‘D‘) add_edge(C.x);
172             if(C.type == ‘C‘) change_w(C.x, C.p);
173             if(C.type == ‘Q‘) querycnt(C.x, C.p);
174         }
175         printf("Case %d: %.6lf\n", ++kase, sum/double(cnt));
176     }
177     return 0;
178 }
时间: 2024-12-24 23:07:00

LA 5031 Graph and Queries —— Treap名次树的相关文章

UVaLive 5031 Graph and Queries (Treap)

Graph and Queries Description You are given an undirected graph with N vertexes and M edges. Every vertex in this graph has an integer value assigned to it at the beginning. You’re also given a sequence of operations and you need to process them as r

uvalive 5031 Graph and Queries 名次树+Treap

题意:给你个点m条边的无向图,每个节点都有一个整数权值.你的任务是执行一系列操作.操作分为3种... 思路:本题一点要逆向来做,正向每次如果删边,复杂度太高.逆向到一定顺序的时候添加一条边更容易.详见算法指南P235. 1 #include<cstdlib> 2 3 struct Node 4 { 5 Node *ch[2]; // 左右子树 6 int r; // 随机优先级 7 int v; // 值 8 int s; // 结点总数 9 Node(int v):v(v) 10 { 11

UVa 1479 (Treap 名次树) Graph and Queries

这题写起来真累.. 名次树就是多了一个附加信息记录以该节点为根的树的总结点的个数,由于BST的性质再根据这个附加信息,我们可以很容易找到这棵树中第k大的值是多少. 所以在这道题中用一棵名次树来维护一个连通分量. 由于图中添边比较方便,用并查集来表示连通分量就好了,但是删边不太容易实现. 所以,先把所有的边删去,然后逆序执行命令.当然,C命令也要发生一些变化,比如说顺序的情况是从a变成b,那么逆序执行的话应该就是从b变成a. 最后两棵树的合并就是启发式合并,把节点数少的数并到节点数多的数里去. 1

HDU 3726 Graph and Queries treap树

题目来源:HDU 3726 Graph and Queries 题意:见白书 思路:刚学treap 參考白皮书 #include <cstdio> #include <cstring> #include <cstdlib> using namespace std; struct Node { Node *ch[2]; int r; int v; int s; Node(int v): v(v) { ch[0] = ch[1] = NULL; r = rand(); s

UVA 1479 - Graph and Queries(Treap)

UVA 1479 - Graph and Queries 题目链接 题意:给定一个n个结点m条边的无向图,每个结点一个权值,现在有3种操作 D x,删除id为x的边 Q x k 计算与x结点的连通分量中第k大的数字,不存在就是0 C x v 把x结点权值改为v 要求计算所有Q操作的和除以Q操作的次数的值 思路:Treap的经典题,进行离线操作,把操作全部逆向进行,删边就可以转化为加边,就可以利用并查集,那么维护第k大就利用Treap 代码: #include <cstdio> #include

UVALive 5031 Graph and Queries (Treap)

删除边的操作不容易实现一般就是先离线然后逆序来做. 逆序就变成了合并,用并存集判断连通,用Treap树来维护一个连通分量里的名次. Treap = Tree + Heap.就是用一个随机的优先级来平衡树. 名次查询需要维护树的结点数量,假设当前在u点,u的左子树有n个结点,那么u的就是以u为根的树上第n+1小的. 如果查询的不是n+1,那么根据结点数量判断一下在哪颗子树上,然后去查询. 树的合并就将结点数少的树上的点往结点数多的树里面插,然后删掉结点少的树. 修改权值就分解成删除点和插点. 写的

HDU 3726 Graph and Queries Treap

这是一个比较全面的题,涉及到了添加删除寻找第k大还有树的合并. 做法大概先执行所有的删边操作,建立最终的图,这里可以用并查集维护一下, 方便判断是不是在一个联通块中,然后对每个子块建立一个Treap,如果遇到添加边导致两个联通块合并成一个的情况,就将两棵树当中小的那个合并到大的那个里面.因为每次这样的合并操作,必然会有小的那个大小翻倍,其实复杂度是科学的,所以合并操作只要很裸很裸的一个一个节点插入就好. 这题有点考验代码能力,写起来比较的麻烦. #include <cstdio> #inclu

Treap 实现名次树

在主流STL版本中,set,map,都是BST实现的,具体来说是一种称为红黑树的动态平衡BST: 但是在竞赛中并不常用,因为红黑树过于复杂,他的插入 5 种,删除 6 中,代码量极大(如果你要改板子的话): 相比之下有一种Treap的动态平衡BST,却也可以做到插入,删除,查找的期望时间复杂度O(logn): 结点定义: struct Node { Node *ch[2]; int r; //优先级 int v; //值 int s; //结点总数 Node(int v):v(v) { ch[0

Treap和名次树

Treap名字的来源:Tree+Heap,正如名字一样,就是一颗简单的BST,一坨堆的合体.BST的不平衡的根本原因在于基于左<=根<=右的模式吃单调序列时候会无脑成长链,而Treap则添加一个优先级属性,值的大小随机生成,用最大堆的方式维护.之所以使用堆,是因为堆是一颗 完全二叉树,而BST梦寐以求的就是完全二叉结构,二者一结合,就产生了一种新的Balanced BST.Treap依赖于随机数,随机生成的优先级属性,通过简单的左右旋可以将长链旋转成近似完全二叉树结构,注意只是近似,平均情况下