题目链接
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
题意:n个结点,m条边的无向图,每个节点有一个整数权值。
3种操作:
D X,删除id为X的边。输入保证每条边至多被删除一次。
Q X k , 计算与结点X联通的结点中(包括X),第K大的权值。如果不存在,输出0。
C X V,把结点X的权值改为V
输出所有询问的平均值。
题解:离线操作,我们把操作倒过来,把删边操作转换为加边。由于要询问第K大,所以我们用平衡树维护一个联通块中的点。当我们要合并两个联通块时,将结点数小的平衡树中的点加入另一个平衡树,删除结点数小的平衡树,这样的合并称为启发式合并。我们来分析一下复杂度,由于1个点合并以后,它所在的树的范围至少增大一倍。所以这个点最多合并logn次,每次合并的复杂度为logn,所以合并的总复杂度为O(n*logn*logn)。单次询问和修改的复杂度为logn。
代码如下:
#include<stdio.h> #include<iostream> #include<algorithm> #include<map> #include<string.h> #include<vector> #define nn 21000 #define mod 100003 typedef long long LL; typedef unsigned long long LLU; const double eps=1e-8; using namespace std; int n,m; int a[nn]; struct node { char c; int x,k; }ask[nn*30]; int u[nn*3],v[nn*3]; bool use[nn*3]; int fa[nn]; struct node1 { int val; int key; int num; int sum; node1* son[2]; }; node1* Tree[nn]; void update(node1* id) { if(id==NULL) return ; id->sum=id->num; if(id->son[0]!=NULL) id->sum+=id->son[0]->sum; if(id->son[1]!=NULL) id->sum+=id->son[1]->sum; } void Rotate(node1* &id,int d) { node1* tem=id->son[d]; id->son[d]=tem->son[d^1]; tem->son[d^1]=id; update(id); update(tem); id=tem; } void Insert(node1* &id,int val) { if(id==NULL) { id=new node1; id->val=val; id->key=rand()*rand(); id->num=id->sum=1; id->son[0]=id->son[1]=NULL; return ; } if(id->val==val) { id->num++; id->sum++; } else if(val<id->val) { Insert(id->son[0],val); update(id); if(id->son[0]->key<id->key) { Rotate(id,0); } } else { Insert(id->son[1],val); update(id); if(id->son[1]->key<id->key) { Rotate(id,1); } } } void Remove(node1* &id,int val) { if(id==NULL) return ; if(id->val==val) { if(id->num==1) { if(id->son[0]==NULL) { node1* tem=id; id=id->son[1]; delete tem; } else if(id->son[1]==NULL) { node1* tem=id; id=id->son[0]; delete tem; } else { if(id->son[0]->key<id->son[1]->key) { Rotate(id,0); Remove(id->son[1],val); } else { Rotate(id,1); Remove(id->son[0],val); } } } else id->num--; } else if(id->val>val) Remove(id->son[0],val); else Remove(id->son[1],val); update(id); } int Kth(node1* id,int k) { if(id==NULL||k<=0||k>id->sum) return 0; int tem=0; if(id->son[1]!=NULL) tem+=id->son[1]->sum; if(tem>=k) { return Kth(id->son[1],k); } else if(tem+id->num>=k) { return id->val; } else return Kth(id->son[0],k-tem-id->num); } int findfa(int x) { if(fa[x]==x) return x; return fa[x]=findfa(fa[x]); } void Union(node1* &id,int y) { if(id==NULL) return ; Union(id->son[0],y); Union(id->son[1],y); for(int i=0;i<id->num;i++) { Insert(Tree[y],id->val); } delete id; id=NULL; } void Clear(node1* &id) { if(id==NULL) return ; Clear(id->son[0]); Clear(id->son[1]); delete id; id=NULL; } int main() { int i; char s[5]; int cas=1; while(scanf("%d%d",&n,&m)&&(n+m)) { memset(use,false,sizeof(use)); for(i=1;i<=n;i++) { scanf("%d",&a[i]); } for(i=1;i<=m;i++) { scanf("%d%d",&u[i],&v[i]); } int la=0; while(1) { scanf("%s",s); if(s[0]=='E') break; la++; ask[la].c=s[0]; if(s[0]=='D') { scanf("%d",&ask[la].x); use[ask[la].x]=true; ask[la].k=-1; } else { scanf("%d%d",&ask[la].x,&ask[la].k); if(s[0]=='C') { int tem=a[ask[la].x]; a[ask[la].x]=ask[la].k; ask[la].k=tem; } } } for(i=1;i<=n;i++) { fa[i]=i; Tree[i]=NULL; Insert(Tree[i],a[i]); } for(i=1;i<=m;i++) { if(!use[i]) { int fx=findfa(u[i]); int fy=findfa(v[i]); if(fx!=fy) { if(Tree[fx]->sum<Tree[fy]->sum) { fa[fx]=fy; Union(Tree[fx],fy); } else { fa[fy]=fx; Union(Tree[fy],fx); } } } } double ans=0; int q=0; for(i=la;i>=1;i--) { if(ask[i].c=='D') { int fx=findfa(u[ask[i].x]); int fy=findfa(v[ask[i].x]); if(fx!=fy) { if(Tree[fx]->sum<Tree[fy]->sum) { fa[fx]=fy; Union(Tree[fx],fy); } else { fa[fy]=fx; Union(Tree[fy],fx); } } } else if(ask[i].c=='Q') { q++; ans+=Kth(Tree[findfa(ask[i].x)],ask[i].k); } else { int ix=findfa(ask[i].x); Remove(Tree[ix],a[ask[i].x]); a[ask[i].x]=ask[i].k; Insert(Tree[ix],ask[i].k); } } for(i=1;i<=n;i++) { Clear(Tree[i]); } printf("Case %d: %.6lf\n",cas++,double(ans)/q); } return 0; }