题意:给定一个无向图,每个节点都赋予一个权值,给出三种操作:
- 删除一条边。
- 询问节点x所在连通分量的第k大的权值。
- 修改节点x的权值为k。
可以想到为每一个连通分量建一颗treap,修改操作很好实现,先删后加就行。但是对于删除操作,如果把一个连通分量分成了两个,那就要先把分出去的全删了,再建一棵treap,时间复杂度和代码复杂度都很操蛋的。我们可以反过来想:先把最终的图的treap建好,然后再将其复原回去,并在该过程中执行询问,这样修改操作没变,删除操作就变成了把两个连通分量合并(如果该边的两个顶点属于不同的连通分量),对应在treap里就是把两颗树合并可以先把节点个数小的treap的每个节点插入到另外一个里。
另外,我给treap节点加了一个id域,指明对应图中的节点的id,方便在合并的时候同时更新并查集。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
const int maxn = 20000 + 10;
struct node
{
node * ch[2];
int r, v, s, id;
void pushup()
{
s = 1;
if(ch[0] != NULL) s += ch[0]->s;
if(ch[1] != NULL) s += ch[1]->s;
}
};
node * root[maxn];
int pre[maxn], val[maxn];
int n, m;
struct pp
{
int cas, x, k;
}que[600000];
struct pp1
{
int u, v, flag;
}edge[60010];
int Find(int a)
{
if(a != pre[a]) return pre[a] = Find(pre[a]);
else return a;
}
inline void rotate(node * & o, int d) // d = 0 左旋 d = 1 右旋
{
node * k = o->ch[d ^ 1]; o->ch[d ^ 1] = k->ch[d]; k->ch[d] = o; o = k; k->ch[d]->pushup(); k->pushup();
}
void insert(node * & o, int x, int id)
{
if(o == NULL) { o = new node(); o->ch[0] = o->ch[1] = NULL; o->v = x; o->r = rand(); o->id = id;}
else
{
int d = (x < o->v || (x == o->v && id < o->id)) ? 0 : 1;
insert(o->ch[d], x, id);
if(o->ch[d]->r > o->r) rotate(o, d ^ 1);
}
o->pushup();
}
void remove(node * & o, int x, int id)
{
if(o == NULL) return;
int d = ((x < o->v || (x == o->v && id < o->id)) ? 0 : 1);
if(o->v == x && id == o->id)
{
node * tmp = o;
if(o->ch[0] == NULL) { o = o->ch[1]; delete tmp; }
else if(o ->ch[1] == NULL) { o = o->ch[0]; delete tmp; }
else
{
int d2 = (o->ch[0]->r > o->ch[1]->r) ? 1 : 0;
rotate(o, d2); remove(o->ch[d2], x, id);
}
}
else remove(o->ch[d], x, id);
if(o != NULL) o->pushup();
}
int findk(node * o, int k)
{
int tmp = 0;
if(o->ch[1] != NULL) { tmp += o->ch[1]->s; if(tmp >= k) return findk(o->ch[1], k); }
tmp++; if(tmp == k) return o->v;
if(o->ch[0] != NULL) { if(k > tmp) return findk(o->ch[0], k - tmp); }
return 0;
}
void merge(int id, node * & a, node * & b)
{
if(b->ch[0] != NULL) merge(id, a, b->ch[0]);
if(b->ch[1] != NULL) merge(id, a, b->ch[1]);
pre[b->id] = id; insert(a, b->v, b->id); delete b; b = NULL;
}
int main()
{
freopen("in", "r", stdin);
int cc = 1;
while(~scanf("%d %d", &n, &m) && n && m)
{
for(int i = 1; i <= n; ++i) scanf("%d", val + i);
for(int i = 1; i <= m; ++i)
{
scanf("%d %d", &edge[i].u, &edge[i].v);
edge[i].flag = 1;
}
char s[10]; int c = 0;
while(~scanf("%s", s) && s[0] != 'E')
{
c++;
if(s[0] == 'D')
{
que[c].cas = 1;
scanf("%d", &que[c].x);
edge[que[c].x].flag = 0;
}
else if(s[0] == 'Q')
{
que[c].cas = 2;
scanf("%d %d", &que[c].x, &que[c].k);
}
else
{
que[c].cas = 3;
scanf("%d", &que[c].x);
que[c].k = val[que[c].x];
scanf("%d", &val[que[c].x]);
}
}
printf("Case %d: ", cc++);
for(int i = 1; i <= n; ++i) { root[i] = NULL; insert(root[i], val[i], i); pre[i] = i; }
for(int i = 1; i <= m; ++i)
{
if(edge[i].flag != 0)
{
int tmpa = Find(edge[i].u), tmpb = Find(edge[i].v);
if(tmpa == tmpb) continue;
if(root[tmpa]->s > root[tmpb]->s) merge(tmpa, root[tmpa], root[tmpb]);
else merge(tmpb, root[tmpb], root[tmpa]);
}
}
double res = 0, nn = 0;
for(int i = c; i > 0; --i)
{
if(que[i].cas == 1)
{
int tmpa = Find(edge[que[i].x].u), tmpb = Find(edge[que[i].x].v);
if(tmpa == tmpb) continue;
if(root[tmpa]->s > root[tmpb]->s) merge(tmpa, root[tmpa], root[tmpb]);
else merge(tmpb, root[tmpb], root[tmpa]);
}
else if(que[i].cas == 2)
{
nn++;
res += findk(root[Find(que[i].x)], que[i].k);
}
else
{
int tmp = Find(que[i].x);
remove(root[tmp], val[que[i].x], que[i].x);
insert(root[tmp], que[i].k, que[i].x);
val[que[i].x] = que[i].k;
}
}
if(nn != 0) printf("%.6lf\n", res / nn);
else printf("0\n");
}
return 0;
}
/*
3 3
10
20
30
1 2
2 3
1 3
D 3
Q 1 2
Q 2 1
D 2
Q 3 2
C 1 50
Q 1 1
E
3 3
10
20
20
1 2
2 3
1 3
Q 1 1
Q 1 2
Q 1 3
E
0 0
Case 1: 25.000000
Case 2: 16.666667
*/