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         ch[0] = ch[1] = NULL;
 12         r = rand();
 13         s = 1;
 14     }
 15     int cmp(int x) const
 16     {
 17         if (x == v) return -1;
 18         return x < v ? 0 : 1;
 19     }
 20     void maintain()
 21     {
 22         s = 1;
 23         if(ch[0] != NULL) s += ch[0]->s;
 24         if(ch[1] != NULL) s += ch[1]->s;
 25     }
 26 };
 27
 28 void rotate(Node* &o, int d)
 29 {
 30     Node* k = o->ch[d^1];
 31     o->ch[d^1] = k->ch[d];
 32     k->ch[d] = o;
 33     o->maintain();
 34     k->maintain();
 35     o = k;
 36 }
 37
 38 void insert(Node* &o, int x)
 39 {
 40     if(o == NULL) o = new Node(x);
 41     else
 42     {
 43         int d = (x < o->v ? 0 : 1); // 不要用cmp函数,因为可能会有相同结点
 44         insert(o->ch[d], x);
 45         if(o->ch[d]->r > o->r) rotate(o, d^1);
 46     }
 47     o->maintain();
 48 }
 49
 50 void remove(Node* &o, int x)
 51 {
 52     int d = o->cmp(x);
 53     int ret = 0;
 54     if(d == -1)
 55     {
 56         Node* u = o;
 57         if(o->ch[0] != NULL && o->ch[1] != NULL)
 58         {
 59             int d2 = (o->ch[0]->r > o->ch[1]->r ? 1 : 0);
 60             rotate(o, d2);
 61             remove(o->ch[d2], x);
 62         }
 63         else
 64         {
 65             if(o->ch[0] == NULL) o = o->ch[1];
 66             else o = o->ch[0];
 67             delete u;
 68         }
 69     }
 70     else
 71         remove(o->ch[d], x);
 72     if(o != NULL) o->maintain();
 73 }
 74
 75 #include<cstdio>
 76 #include<cstring>
 77 #include<vector>
 78 using namespace std;
 79
 80 const int maxc = 500000 + 10;
 81 struct Command
 82 {
 83     char type;
 84     int x, p; // 根据type, p代表k或者v
 85 } commands[maxc];
 86
 87 const int maxn = 20000 + 10;
 88 const int maxm = 60000 + 10;
 89 int n, m, weight[maxn], from[maxm], to[maxm], removed[maxm];
 90
 91 // 并查集相关
 92 int pa[maxn];
 93 int findset(int x)
 94 {
 95     return pa[x] != x ? pa[x] = findset(pa[x]) : x;
 96 }
 97
 98 // 名次树相关
 99 Node* root[maxn]; // Treap
100
101 int kth(Node* o, int k)   // 第k大的值
102 {
103     if(o == NULL || k <= 0 || k > o->s) return 0;
104     int s = (o->ch[1] == NULL ? 0 : o->ch[1]->s);
105     if(k == s+1) return o->v;
106     else if(k <= s) return kth(o->ch[1], k);
107     else return kth(o->ch[0], k-s-1);
108 }
109
110 void mergeto(Node* &src, Node* &dest)
111 {
112     if(src->ch[0] != NULL) mergeto(src->ch[0], dest);
113     if(src->ch[1] != NULL) mergeto(src->ch[1], dest);
114     insert(dest, src->v);
115     delete src;
116     src = NULL;
117 }
118
119 void removetree(Node* &x)
120 {
121     if(x->ch[0] != NULL) removetree(x->ch[0]);
122     if(x->ch[1] != NULL) removetree(x->ch[1]);
123     delete x;
124     x = NULL;
125 }
126
127 // 主程序相关
128 void add_edge(int x)
129 {
130     int u = findset(from[x]), v = findset(to[x]);
131     if(u != v)
132     {
133         if(root[u]->s < root[v]->s)
134         {
135             pa[u] = v;
136             mergeto(root[u], root[v]);
137         }
138         else
139         {
140             pa[v] = u;
141             mergeto(root[v], root[u]);
142         }
143     }
144 }
145
146 int query_cnt;
147 long long query_tot;
148 void query(int x, int k)
149 {
150     query_cnt++;
151     query_tot += kth(root[findset(x)], k);
152 }
153
154 void change_weight(int x, int v)
155 {
156     int u = findset(x);
157     remove(root[u], weight[x]);
158     insert(root[u], v);
159     weight[x] = v;
160 }
161
162 int main()
163 {
164     int kase = 0;
165     while(scanf("%d%d", &n, &m) == 2 && n)
166     {
167         for(int i = 1; i <= n; i++) scanf("%d", &weight[i]);
168         for(int i = 1; i <= m; i++) scanf("%d%d", &from[i], &to[i]);
169         memset(removed, 0, sizeof(removed));
170
171         // 读命令
172         int c = 0;
173         for(;;)
174         {
175             char type;
176             int x, p = 0, v = 0;
177             scanf(" %c", &type);
178             if(type == ‘E‘) break;
179             scanf("%d", &x);
180             if(type == ‘D‘) removed[x] = 1;
181             if(type == ‘Q‘) scanf("%d", &p);
182             if(type == ‘C‘)
183             {
184                 scanf("%d", &v);
185                 p = weight[x];
186                 weight[x] = v;
187             }
188             commands[c++] = (Command)
189             {
190                 type, x, p
191             };
192         }
193
194         // 最终的图
195         for(int i = 1; i <= n; i++)
196         {
197             pa[i] = i;
198             if(root[i] != NULL) removetree(root[i]);
199             root[i] = new Node(weight[i]);
200         }
201         for(int i = 1; i <= m; i++) if(!removed[i]) add_edge(i);
202
203         // 反向操作
204         query_tot = query_cnt = 0;
205         for(int i = c-1; i >= 0; i--)
206         {
207             if(commands[i].type == ‘D‘) add_edge(commands[i].x);
208             if(commands[i].type == ‘Q‘) query(commands[i].x, commands[i].p);
209             if(commands[i].type == ‘C‘) change_weight(commands[i].x, commands[i].p);
210         }
211         printf("Case %d: %.6lf\n", ++kase, query_tot / (double)query_cnt);
212     }
213     return 0;
214 }

时间: 2024-10-11 17:08:54

uvalive 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)

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

LA 5031 Graph and Queries —— Treap名次树

离线做法,逆序执行操作,那么原本的删除边的操作变为加入边的操作,用名次树维护每一个连通分量的名次,加边操作即是连通分量合并操作,每次将结点数小的子树向结点数大的子树合并,那么单次合并复杂度O(n1logn2),由于合并之后原本结点数少的子树结点数至少翻倍,所以每个结点最多被插入 logn 次,故总时间复杂度为 O(n log2n)  . 注意细节处理,代码如下: 1 #include <cstdio> 2 #include <cstdlib> 3 #include <cstr

uva 1479 - Graph and Queries(伸展树)

题目链接:uva 1479 - Graph and Queries 题目大意:有一张m条边的无向图,每个节点都有一个权值,现在有若干个操作, D x:删除ID为x的节点 Q x k:计算与节点x联通的节点当中,第k大的权值 C x v:把节点x的权值改为v 解题思路:把所有操作反过来处理,先执行所有的D操作,获得最终的图,然后逆操作的时候对于D来说即为合并操作,Q和C则是查询和修改操作. #include <cstdio> #include <cstring> #include &

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

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依赖于随机数,随机生成的优先级属性,通过简单的左右旋可以将长链旋转成近似完全二叉树结构,注意只是近似,平均情况下

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