关于启发式合并
在我们愉快打暴力的时候,我们会遇到需要合并一些数据的情况。
我们举一个相当简单的例子,我们需要很多次合并一些 vector,这个时候作为人类我们会想从小的里边取放到大的里边。
若我们需要大到小,就先反过来,再利用对应标记呼唤的方式来进行访问。
然而对于 stl 来世 swap 就可以。
整个过程是 \(nlogn\) 级别的。
对于任意一个元素,每一次被访问都会使得它所在的集合至少增长一倍,所以就是 \(nlogn\) 级别的。
[PA 2014] Fiolki
这个东西我们并不知道怎么做,但是发现如果我们在合并的过程之中可以维护每个集合的东西就可以做了。
考虑 vector 维护每个集合中存在的,可能的反应式(这个东西有严格的反应顺序)。
之后启发式合并两个,并查集方便我们检查反应是否可以进行。
非常完美做完了。
代码↓
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MN=1e6+116;
int father[MN], n, m, k, ans=0;
int g[MN], a[MN], b[MN], c[MN], d[MN];
int find(int x){if(father[x]!=x) father[x]=find(father[x]);return father[x];
}
vector <int> st[MN];
signed main(){ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);cin>>n>>m>>k; for(int i=1; i<=n; ++i) cin>>g[i];for(int i=1; i<=n; ++i) father[i]=i;for(int i=1; i<=m; ++i) cin>>a[i]>>b[i];for(int i=1; i<=k; ++i){cin>>c[i]>>d[i];st[c[i]].push_back(i);st[d[i]].push_back(i);}for(int i=1; i<=m; ++i){if(st[a[i]].size()>st[b[i]].size())swap(st[a[i]],st[b[i]]);father[find(a[i])]=find(b[i]);vector <int> tmp;for(auto v:st[a[i]]){if(find(c[v])==find(d[v])) tmp.push_back(v);else st[b[i]].push_back(v);}sort(tmp.begin(),tmp.end());for(auto k:tmp){int minn=min(g[c[k]],g[d[k]]);ans+=minn*2;g[c[k]]-=minn;g[d[k]]-=minn;}}cout<<ans<<'\n';return 0;
}