UVA1479 Graph and Queries

\(\texttt{Description}\)

洛谷链接

UVA 链接

  • 有一张 \(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,不然会导致在访问当前迭代器下一个元素时 ++itit 失效,引起 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';//要求输出的是平均数。
    }
}
posted @ 2023-01-23 22:52  lzyqwq  阅读(57)  评论(0)    收藏  举报