题目:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=20332
题意:给定无向图,有三种操作,①删除第i条边②查询节点x所在的连通分量节点中第K大的权值③改变节点x的权值。现在问你最终的查询结果的平均值。
分析:查询第k大的值,用线段树可以,但是这里显然有很多连通分量,用线段树的话内存不够。这题是lrj白书上面的一题。由于第一次写Treap,还是仔细说一下。
Treap就是普通的Bst+Heap,Bst是为了有序,而Heap是为了让树平衡。
由于本题删边会导致树分裂,不好处理。可以用离线算法。
首先获得所有操作后图的最终状态。然后把操作顺序反过来。
删边就处理成加边,然后将集合和树合并,树合并要用启发式合并,就是规模小的树合并到大的树中去。这样对于在规模小的树中的节点而言,树的规模是成倍增长的,所以最多合并logn次,即所有的节点都最多移动logn次。
ps:初始化图的时候注意③操作。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
const LL INF = 1E9+9;
typedef pair <int,int> Pii;
typedef tuple<char,int,int> Tcii;
struct node //定义Treap节点
{
node* son[2]; //左右子树
int v; //值
int p; //优先级
int sz; //以当前节点为根节点的树的大小
node(int x)
{
son[0]=son[1]=NULL;
v=x;
p=rand();
sz=1;
}
void pushup() //增加或删除节点需要修改子树的sz
{
sz=1;
if(son[0]) sz+=son[0]->sz;
if(son[1]) sz+=son[1]->sz;
}
};
inline int cmp(int a,int b)
{
if(a==b) return -1;
return a>b?0:1;
}
inline void Rotate(node* &root,int d) //d为0时左旋(右节点向上更新), d为1时右旋(左节点向上更新)
{
node* k = root->son[d^1];
root->son[d^1] = k->son[d] ;
k->son[d] = root;
root->pushup();
k->pushup();
root = k ;
}
void Insert(node* &root,int x)
{
if(NULL==root)
root = new node(x);
else
{
int d = root->v>x?0:1; //这样写的话支持相同元素插入
Insert(root->son[d],x);
if(root->p<root->son[d]->p)
Rotate(root,d^1);
}
if(root)
root->pushup();
}
void Erase(node* &root,int x)
{
int d = cmp(root->v,x);
if(-1==d)
{
node* u = root;
if(!root->son[0])
{
root = root->son[1];
delete u;
}
else if(!root->son[1])
{
root = root->son[0];
delete u;
}
else
{
int d2 = (root->son[0]->p>root->son[1]->p?1:0);
Rotate(root,d2);
Erase(root->son[d2],x);
}
}
else
Erase(root->son[d],x);
if(root)
root->pushup();
}
void Destroy(node* &root)
{
if(root->son[0])
Destroy(root->son[0]);
if(root->son[1])
Destroy(root->son[1]);
delete root;
root=NULL;
}
int Kth(node* &root,int k)
{
int lnum=0;
if(root->son[0])
lnum+=root->son[0]->sz;
if(k==lnum+1)
return root->v;
if(k>lnum+1)
return Kth(root->son[1],k-lnum-1);
else
return Kth(root->son[0],k);
}
void MoveTree(node* &root1,node* &root2) //将以root1为根的树移到以root2为根的树上,同时销毁以root1为根的树
{
if(root1->son[0])
MoveTree(root1->son[0],root2);
if(root1->son[1])
MoveTree(root1->son[1],root2);
Insert(root2,root1->v);
delete root1;
root1=NULL;
}
const int N = 20000 + 666;
const int M = 60000 + 666;
int fa[N],w[N],nCase;
Pii edge[M];
int getOut[M],q;
Tcii Qu[555555];
node* RankTree[N]; //N棵名次树
int FindSet(int cur)
{
if(fa[cur]==cur)
return cur;
return fa[cur]=FindSet(fa[cur]);
}
void Merge(int u,int v) // 合并集合,同时合并名次树
{
int s1=FindSet(u);
int s2=FindSet(v);
if(s1==s2)
return ;
if(RankTree[s1]->sz>RankTree[s2]->sz)
{
fa[s2]=s1;
MoveTree(RankTree[s2],RankTree[s1]);
}
else
{
fa[s1]=s2;
MoveTree(RankTree[s1],RankTree[s2]);
}
}
void input(int n,int m)
{
fill(getOut,getOut+m+1,0);
for(int i=1;i<=n;i++)
scanf("%d",&w[i]);
for(int i=1;i<=m;i++)
scanf("%d%d",&edge[i].first,&edge[i].second);
q=0;
char op[2];
while(scanf("%s",op),op[0]!='E')
{
int x=0,y=0;
if(op[0]=='D')
{
scanf("%d",&x);
Qu[q]=make_tuple('D',x,y);
getOut[x]=1;
}
else if(op[0]=='Q')
{
scanf("%d%d",&x,&y);
Qu[q]=make_tuple('Q',x,y);
}
else
{
scanf("%d%d",&x,&y);
Qu[q]=make_tuple('C',x,w[x]);
w[x]=y;
}
q++;
}
}
void Init(int n,int m)
{
for(int i=1;i<=n;i++)
{
if(RankTree[i])
Destroy(RankTree[i]);
RankTree[i] = new node(w[i]);
fa[i]=i;
}
for(int i=1;i<=m;i++) if(!getOut[i])
Merge(edge[i].first,edge[i].second);
}
void solve(int n,int m)
{
LL sum=0,times=0;
for(int i=q-1;i>=0;i--)
{
char com = get<0>(Qu[i]);
int x = get<1>(Qu[i]),y=get<2>(Qu[i]);
if(com=='D')
Merge(edge[x].first,edge[x].second);
else if('Q'==com)
{
int s=FindSet(x);
if(1<=y && y<=RankTree[s]->sz)
sum+=Kth(RankTree[s],RankTree[s]->sz-y+1);
times++;
}
else
{
int s=FindSet(x);
Erase(RankTree[s],w[x]);
Insert(RankTree[s],y);
w[x]=y;
}
}
printf("Case %d: %.6lf\n",++nCase,(double)sum/times);
}
int main()
{
int n,m;
while((scanf("%d%d",&n,&m)==2)&&n)
{
input(n,m);
Init(n,m);
solve(n,m);
}
return 0;
}