UVA1479 Graph and Queries
\(\texttt{Description}\)
有一张 \(n\) 个节点 \(m\) 条边的无向图,第 \(i\) 个点有点权 \(w_i\),还有 \(3\) 种操作:
\(\texttt{D }x\):删除第 \(x\) 条边,保证每条边至多被删除一次。
\(\texttt{C }x\texttt{ }v\):将 \(w_x\) 修改成 \(v\)。
\(\texttt{Q }x\texttt{ }k\):查询在点 \(x\) 所在的极大连通分量中,第 \(k\) 大的点权(即将点权从大到小排序后排在第 \(k\) 位的数)。
多组数据,每组数据 \(1\le n\le 2\times 10^4\),\(1\le m\le6\times 10^4\),\(\texttt{Q}\) 和 \(\texttt{C}\) 操作次数不超过 \(2\times 10^5\)。
\(\texttt{Solution}\)
删边这个操作不太好搞,所以考虑将操作离线倒着加边做。所以可以先处理出所有操作都做完后的图与点权。这样删边就变成了加边,再维护极大连通分量。自然地想到并查集。
由于还要查询第 \(k\) 大点权,考虑在每个集合根部维护平衡树。这里平衡树用 pb_ds 中的 rb_tree_tag 实现。
对于修改操作,可以想象成是先删除原来的点权,再加入新的点权(这里新的点权指修改之前的点权,因为倒过来操作了)。由于点权有重复,所以平衡树的类型是 pair,第二关键字放点编号,用于避免重复。
对于加边操作,为了降低复杂度,考虑启发式合并,即把小集合合并到大集合上。这一步操作并不能直接用 pb_ds 中的 join,据说是只有一棵树里的值的优先级全部比另一棵树小才可以用 join。所以要遍历小集合维护的平衡树,将其中的元素插入大集合维护的平衡树中。注意这里要清空小集合维护的平衡树,不然一个点就会存在于多棵平衡树里,导致空间复杂度过高。
清空还有个小细节,不能边遍历边删除迭代器 it,不然会导致在访问当前迭代器下一个元素时 ++it 中 it 失效,引起 RE。正确的做法是遍历时只插入新元素,遍历完之后再用 clear 清空。
时间复杂度为 \(\mathcal{O}(T(q+m)(\log n+\alpha(n)))\)(\(T\) 为数据组数,\(q\) 为操作总数,\(\alpha(n)\) 为启发式合并复杂度中的反阿克曼函数),空间复杂度为 \(\mathcal{O}(n+m+q)\),可以接受。
\(\texttt{Code}\)
$\texttt{Click Here}$
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
#define int long long//值域有点大。
using namespace std;
int n,m,T,la[20005],cnt,f[20005],ans,tot;
char type;
bool del[60005];
__gnu_pbds::tree<pair<int,int>,__gnu_pbds::null_type,greater<pair<int,int>>,__gnu_pbds::rb_tree_tag,__gnu_pbds::tree_order_statistics_node_update>t[20005];
struct edge{
int u,v;
}e[60005];
struct query{
char c;
int x,k;
}op[500005];
int find(int x){//个人的启发式合并写法,根节点的父亲为极大连通分量大小的相反数。
return f[x]<0?x:f[x]=find(f[x]);
}
void merge(int x,int y){
x=find(x),y=find(y);
if(x^y){
if(f[x]>f[y]){//往大的上合并,因为是相反数,所以要往实际值小的上合并。
swap(x,y);
}
for(auto it=t[y].begin();it!=t[y].end();++it){//先插入。
t[x].insert(*it);
}
t[y].clear();//再清空。
f[x]+=f[y];//并集。
f[y]=x;
}
}
signed main(){
cin.tie(0);//读入优化。
cout.tie(0);
ios::sync_with_stdio(0);
while(cin>>n>>m&&n&&m){
cnt=ans=tot=0;
for(int i=1;i<=n;++i){
t[i].clear();//多测清空。
f[i]=-1;//初始大小为 1。
cin>>la[i];
}
for(int i=1;i<=m;++i){//读边。
del[i]=0;
cin>>e[i].u>>e[i].v;
}
while(cin>>type&&type^'E'){//读入操作。
int x,k;
if(type=='D'){//删除。
cin>>x;
del[x]=1;//被删除。
op[++cnt]={'D',x};
}else if(type^'C'){//查询。
cin>>x>>k;
++tot;
op[++cnt]={'Q',x,k};
}else{//修改。
cin>>x>>k;
op[++cnt]={'C',x,la[x]};//倒着来,改成上一个值。
la[x]=k;
}
}
for(int i=1;i<=n;++i){//维护初始平衡树。
t[i].insert(make_pair(la[i],i));
}
for(int i=1;i<=m;++i){//建出操作做完后的图。
if(!del[i]){
merge(e[i].u,e[i].v);
}
}
for(int i=cnt;i;--i){//倒着操作。
if(op[i].c=='Q'){//查询。
if(t[find(op[i].x)].size()>=op[i].k){
ans+=t[find(op[i].x)].find_by_order(op[i].k-1)->first;//第 k 大点权,pb_ds 中排名是从 0 开始的,故 -1。
}
}else if(op[i].c^'C'){//加边。
merge(e[op[i].x].u,e[op[i].x].v);
}else{//修改。
auto it=t[find(op[i].x)].find(make_pair(la[op[i].x],op[i].x));
t[find(op[i].x)].erase(it);//删除原元素。
t[find(op[i].x)].insert(make_pair(la[op[i].x]=op[i].k,op[i].x));//插入新元素。
}
}
cout<<"Case "<<++T<<": "<<fixed<<setprecision(6)<<ans*1.0/tot<<'\n';//要求输出的是平均数。
}
}

浙公网安备 33010602011771号