\[ \text{Preface} \]
算是一道思维难度稍易,代码难度稍难的题吧。
\[
\text{Description}
\]
给出一张 \(n\) 个点,\(m\) 条边的图,点带权。需要支持三个操作:
D x
删掉编号为 \(x\) 的边Q x k
查询与节点 \(x\) 联通的所有节点中,点权第 \(k\) 大节点的点权C x v
将节点 \(x\) 点权改为 \(v\)
多组数据,每组数据最终需要输出所有查询的平均值 ( 保留 6 位 ) ,没有强制在线。
\[
\text{Solution}
\]
不知道大家有没有做过 这道题 ,推荐先去做一下。
\(~\)
首先,对于同一个连通块里的所有节点,查询与任意一个节点连通的所有节点的第 \(k\) 大,都是查询该连通块里所有节点的第 \(k\) 大。已经很明显可以用并查集维护每个连通块的代表节点,再在这个代表节点上用一个数据结构维护连通块信息,支持合并,查询第 \(k\) 大。
我们发现权值线段树可以做到上述操作,尝试用权值线段树维护,每个节点开一个权值线段树。
\(~\)
对于操作 Q x k
\(:\)
权值线段树基本操作。
对于操作 C x v
\(:\)
我们可以看作是在 \(x\) 这个位置上少了一个原来的点权,再多了一个新的点权,两次插入操作即可解决。
对于操作 D x
\(:\)
\(......\) ,我们发现删掉一条边,不能有效使得一个连通块分裂成两个连通块,并且维护权值线段树。
\(~\)
注意到此题 没有强制在线 ,意味着,我们可以离线地把所有操作都读进来,然后去反着考虑这些询问。
这样一来,D x
操作就可以变为 \(:\) 加入一条编号为 \(x\) 的边。其余的两个操作不变。
我们发现添加一条边很容易维护 \(:\) 找出 \(u,v\) 所在的连通块 \(p,q\) ,若 \(p=q\) ,则无需操作;否则合并权值线段树 \(p\) 和权值线段树 \(q\) ,然后令 \(fa[q]=p\) 。
综上所述,我们就可以用 权值线段树 \(+\) 并查集 解决本题了。\((\) 当然什么 \(splay\),\(treap\) 启发式合并也行 \()\)
时空复杂度 \(\text{O(n log n)}\) 。
\[
\text{Code}
\]
#include<cstdio> #include<cstring> #define RI register int using namespace std; inline int read() { int x=0,f=1;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')f=-f;s=getchar();} while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} return x*f; } const int N=6000100,M=6000100,Q=6000100,MLOGN=50000000; const int INF=1e6; int T; int n,m,q; int cnt; long double ans; int val[N]; struct Edge{ int u; int v; bool del; }e[M]; char opt[Q]; int x[Q],k[Q]; int fa[N]; int get(int x) { if(fa[x]==x)return x; return fa[x]=get(fa[x]); } int tot,root[N]; struct SegmentTree{ int lc,rc; int cnt; }t[MLOGN]; int New() { tot++; t[tot].lc=t[tot].rc=t[tot].cnt=0; return tot; } void insert(int &p,int l,int r,int delta,int val) { if(!p) p=New(); t[p].cnt+=val; if(l==r)return; int mid=(l+r)>>1; if(delta<=mid) insert(t[p].lc,l,mid,delta,val); else insert(t[p].rc,mid+1,r,delta,val); } int merge(int p,int q) { if(!p||!q) return p^q; t[p].cnt+=t[q].cnt; t[p].lc=merge(t[p].lc,t[q].lc); t[p].rc=merge(t[p].rc,t[q].rc); return p; } int ask(int p,int l,int r,int k) { if(l==r) return l; int mid=(l+r)>>1; int rcnt=t[t[p].rc].cnt; if(k<=rcnt) return ask(t[p].rc,mid+1,r,k); else return ask(t[p].lc,l,mid,k-rcnt); } void link(int u,int v) { u=get(u),v=get(v); if(u==v)return; root[u]=merge(root[u],root[v]); fa[v]=u; } void work() { tot=cnt=ans=q=0; memset(root,0,sizeof(root)); for(RI i=1;i<=n;i++) val[i]=read(); for(RI i=1;i<=m;i++) e[i].u=read(),e[i].v=read(); char tmp[2]; while(scanf("%s",tmp),tmp[0]!='E') { opt[++q]=tmp[0]; switch(tmp[0]) { case 'D':{ x[q]=read(); e[x[q]].del=true; break; } case 'Q':{ x[q]=read(),k[q]=read(); cnt++; break; } case 'C':{ x[q]=read(),k[q]=val[x[q]],val[x[q]]=read(); break; } } } for(RI i=1;i<=n;i++) fa[i]=i,insert(root[i],-INF,INF,val[i],1); for(RI i=1;i<=m;i++) { if(e[i].del)continue; link(e[i].u,e[i].v); } for(RI i=q;i>=1;i--) switch(opt[i]) { case 'D':{ e[x[i]].del=false; link(e[x[i]].u,e[x[i]].v); break; } case 'Q':{ int p=get(x[i]); int A=ask(root[p],-INF,INF,k[i]); if(A==-INF||A==INF) continue; ans+=(long double)A/cnt; break; } case 'C':{ int p=get(x[i]); insert(root[p],-INF,INF,val[x[i]],-1); val[x[i]]=k[i]; insert(root[p],-INF,INF,val[x[i]],1); break; } } printf("Case %d: %Lf\n",++T,ans); } int main() { while(n=read(),m=read(),n&&m) work(); return 0; }
\[ \text{Thanks} \ \text{for} \ \text{watching} \]
来源:https://www.cnblogs.com/cjtcalc/p/12263001.html