题意:
给一个无向图,再给一系列操作(以下3种),输出最后的平均查询结果。
(1)D X 删除第x条边。
(2)Q X k 查询与点X相连的连通分量中第k大的点的权值。
(3)C X v 将点X的权值改为v。
思路:
第一点,由于需要删除边,不是很方便,所以可以先将所有操作存起来,反序来操作,这样删边就变成加边,方便了不少。每次加边时若两点不属于同个连通分量,就将点少的整个连通分量中的点逐个插入到另一个连通分量中。
第二点,查第k大,这个比较简单,只需要维护Treap上每个点的的左右孩子数量就可以了。查的只是某个点所属连通分量中的第k大值,与真实图是怎样的无关。
第三点,改权值,这个麻烦。可以先删除该点,再插入改过权的该点。删除方式就是将该点旋转到成为叶子,然后删除它就方便了,因为还需要维护堆的性质,所以往下旋转比较方便的。
坑点:随时要注意更新每个连通分量的根节点,也就是说必须用引用的时候,尽量用引用。在第一点操作时,需要插很多点,每插1个点都可能成为根,所以要随时更新根节点。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <bits/stdc++.h> 2 #define pii pair<int,int> 3 #define INF 0x3f3f3f3f 4 #define LL long long 5 using namespace std; 6 const int N=20010; 7 bool single[N]; 8 int w[N], from[N*3], to[N*3], cut[N*3], pre[N], root[N]; 9 struct opera 10 { 11 char type; 12 int a, b; 13 opera(char t,int a,int b):type(t),a(a),b(b){}; 14 }; 15 deque<opera> que; 16 struct node //Treap 17 { 18 int val, pre, rank; //ran是优先级 19 int ch[2], son[2]; 20 node(){}; 21 node(int val):val(val) 22 { 23 rank=rand(); 24 ch[0]=ch[1]=son[0]=son[1]=pre=0; 25 }; 26 }nod[N]; 27 28 int find(int x){return pre[x]==x? x: pre[x]=find(pre[x]);} 29 30 int Query(int t,int k) //找第k大 31 { 32 if( k<1 || nod[t].son[0] + nod[t].son[1] + 1 < k) return 0; //k太小or太大 33 if( nod[t].son[1]==k-1 ) return nod[t].val; 34 if( nod[t].son[1]>k-1 ) return Query(nod[t].ch[1], k); 35 else return Query(nod[t].ch[0], k-nod[t].son[1]-1); 36 } 37 38 void Rotate(int t, int d) //d为方向,0是左旋,1是右 39 { 40 int far=nod[t].pre; 41 int son=nod[t].ch[d]; //far的孩子 42 int gra=nod[far].pre; //far的父亲 43 44 nod[son].pre=far; 45 nod[t].pre=gra; 46 nod[far].pre=t; 47 48 nod[far].ch[d^1]=son; 49 nod[t].ch[d]=far; 50 nod[gra].ch[nod[gra].ch[1]==far]=t; 51 52 //子树中的节点要更新 53 nod[far].son[d^1]=nod[t].son[d]; 54 nod[t].son[d]+=1+nod[far].son[d]; //别忘了还有far也是个节点 55 56 if(gra==0) root[find(far)]=t; //更新连通分量的根节点 57 } 58 59 int get_child(int t) //返回孩子编号,总是返回rank较大的那个。0表示无孩子 60 { 61 if(!nod[t].ch[0] && !nod[t].ch[1]) return 0; 62 if(nod[t].ch[0] && nod[t].ch[1]) 63 { 64 int L=nod[t].ch[0], R=nod[t].ch[1]; 65 if(nod[L].rank < nod[R].rank) return R; 66 else return L; 67 } 68 if(nod[t].ch[0]) return nod[t].ch[0]; 69 else return nod[t].ch[1]; 70 } 71 72 void Insert(int root,int t) //将t插入到root中 73 { 74 if(nod[t].val<nod[root].val) 75 { 76 nod[root].son[0]++; 77 if(nod[root].ch[0]) Insert(nod[root].ch[0], t); 78 else nod[root].ch[0]=t, nod[t].pre=root; 79 } 80 else 81 { 82 nod[root].son[1]++; 83 if(nod[root].ch[1]) Insert(nod[root].ch[1], t); 84 else nod[root].ch[1]=t, nod[t].pre=root; 85 } 86 int son=nod[root].ch[1]; 87 if( nod[son].rank > nod[root].rank ) Rotate(son, 0); //孩子的rank比我还大 88 } 89 void clear(int t,int v) //清空点t,值改为v 90 { 91 nod[t].pre=nod[t].ch[0]=nod[t].ch[1]=nod[t].son[0]=nod[t].son[1]=0; 92 nod[t].val=v; 93 } 94 95 void Merge(int &root, int t) //递归将t中的节点逐个拆出 96 { 97 if(nod[t].ch[0]>0) Merge(root, nod[t].ch[0]); 98 if(nod[t].ch[1]>0) Merge(root, nod[t].ch[1]); 99 clear(t, nod[t].val); //值仍不变 100 single[root]=0; //非单身了 101 single[t]=0; 102 Insert(root, t); //将t插入到root中 103 } 104 105 106 void add_edge(int i) 107 { 108 int u=find(from[i]), v=find(to[i]); //两点所在连通分量 109 if(u!=v) //这里也会去掉重边 110 { 111 int &uu=root[u], &vv=root[v]; //找到连通分量的树根。注意:必须是引用,有可能每插一次就换根了。 112 if( nod[uu].son[0]+nod[uu].son[1] < nod[vv].son[0]+nod[vv].son[1]) //uu小 113 pre[u]=v, Merge(vv, uu); //uu拆出来装到vv上面去 114 else 115 pre[v]=u, Merge(uu, vv); 116 } 117 } 118 119 void change(int t,int v) //将t的权值改为v 120 { 121 if(single[t]==true){nod[t].val=v;return ;} //仅有1个点,直接改 122 int far, son, tmp; 123 while( (son=get_child(t))!=0 ) Rotate( son, nod[t].ch[0]==son); //先把t转到底端 124 tmp=t; 125 while( nod[tmp].pre!=0 ) //调整到根节点的孩子数量,沿途减1。 126 { 127 far=nod[tmp].pre; 128 nod[far].son[nod[far].ch[1]==tmp]--; 129 tmp=far; 130 } 131 far=nod[t].pre; 132 nod[far].ch[ nod[far].ch[1]==t ]=0; //需要做的只是改变父亲的孩子指针。 133 clear(t, v); 134 Insert(root[find(t)], t); //改完值插到root中。 135 } 136 137 double cal(int n,int m) 138 { 139 for(int i=1; i<=n; i++) pre[i]=root[i]=i,nod[i]=node(w[i]); //初始时,root和连通分量编号都是自己 140 for(int i=1; i<=m; i++) if(cut[i]==0) add_edge(i); 141 LL ans=0, cnt=0; 142 while(!que.empty()) 143 { 144 opera p=que.front();que.pop_front(); 145 if(p.type=='D') add_edge(p.a); //加边 146 if(p.type=='Q') cnt++,ans+=Query(root[find(p.a)], p.b); 147 if(p.type=='C') change(p.a, p.b); //改权值 148 } 149 return ans/(double)cnt; 150 } 151 152 int main() 153 { 154 freopen("input.txt", "r", stdin); 155 int a, b, c, n, m, Case=0; 156 char op; 157 while(cin>>n>>m, n+m) 158 { 159 memset(single, 1, sizeof(single)); 160 memset(cut, 0, sizeof(cut)); 161 for(int i=1; i<=n; i++) scanf("%d", &w[i]); 162 for(int i=1; i<=m; i++) scanf("%d %d", &from[i], &to[i]); 163 164 while(cin>>op,op!='E') 165 { 166 if(op=='D'){scanf("%d",&a);cut[a]=1;} //删边 167 if(op=='Q') scanf("%d%d",&a,&b); //查询 168 if(op=='C'){scanf("%d%d",&a,&b),swap(w[a], b);} //改权 169 que.push_front(opera(op, a, b)); 170 } 171 printf("Case %d: %.6f ", ++Case, cal(n, m)); 172 } 173 return 0; 174 }