CF1095F——两种贪心解法的内在一致性;kruskal
Posted hans774882968
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CF1095F——两种贪心解法的内在一致性;kruskal相关的知识,希望对你有一定的参考价值。
官方题解:https://codeforces.com/blog/entry/64130
题意
有两种类型的边。一种是完全图上的边,点x和y的边权是a[x]+a[y]
。另一种是特殊边,由输入给出。取这两种类型的边n-1条,构成一棵MST,求MST边权和。n是经典2e5。
思路
你翻csdn之类的网站的题解,只有一个贪心结论,都不证明一下,真的太逊了。事实上,官方题解给出的“更为复杂的解法”,才是贪心结论的来源。
贪心结论
把m条边和这n-1条边:(x,y),x是某个满足a[x]最小的x,y不等于x,放到一起,跑kruskal。值得注意的是这个贪心结论不是trivial的,是需要推倒的。
关注萌新hans774882968看更多干货QAQ
更为复杂的解法
假设现在已经有若干连通分量,且已知每个连通分量的最小点权a[u[i]]
。那么现在的候选边有两条,一条是边权最小的特殊边e1(特殊边已经升序排序),另一条是点权最小和次小的那两个连通分量里的点u[x][i]
和u[y][j]
所连的边e2,边权a[u[x][i]]+a[u[y][j]]
。e1的两个点可能是在同一个连通分量,此时丢弃即可。而e2必然是不同的连通分量,总是合法的。在e1和e2都合法时,就要选择边权较优秀的那条边。这种情况下,连通分量个数必定减少1。
- 我们用并查集维护连通分量,siz数组来表示连通分量的最小点权。
- e2要快速找到两个点权以及对应的点(作为连通分量的代表元),可以用set。set元素既需要记录最小点权,也要记录代表元对应的点。set元素需要和连通分量的代表元一一对应,所以每次并查集合并操作,都要顺带维护这个set。
这个做法为什么可行?我们发现它其实就是用了一个隐含的双指针,对两个有序数组进行二路归并,并在二路归并的同时跑kruskal。
二者联系
我们在这个set中可以发现一个性质:因为某个点权最小的点x
,它所在的连通分量只会变化不会消失,所以set的第一个元素,选择x
总是合法的(尽管set不一定总是取出这个连通分量,但可以(但没必要)假定一个序关系(如:点编号小的优先),使得x
总是被取出)。这意味着,n-1次合并的非特殊边,事实上可以(不是必定)只来源于x
和所有的y,y≠x。由此我们就得到了以上贪心结论的做法。
代码
贪心结论
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
const int SZ = 2e5 + 5;
int n,m;LL a[SZ];int fa[SZ];
struct Edg{
int x,y;LL w;
}e[SZ*2];
bool cmp(Edg x,Edg y){
return x.w < y.w;
}
int find(int x){return x == fa[x] ? x : fa[x] = find(fa[x]);}
void dbg(){puts("");}
template<typename T, typename... R>void dbg(const T &f, const R &... r) {
cout << f << " ";
dbg(r...);
}
template<typename Type>inline void read(Type &xx){
Type f = 1;char ch;xx = 0;
for(ch = getchar();ch < '0' || ch > '9';ch = getchar()) if(ch == '-') f = -1;
for(;ch >= '0' && ch <= '9';ch = getchar()) xx = xx * 10 + ch - '0';
xx *= f;
}
int main(int argc, char** argv) {
read(n);read(m);
rep(i,1,n) read(a[i]);
re_(i,0,m){
read(e[i].x);read(e[i].y);read(e[i].w);
}
int idx = min_element(a+1,a+n+1)-a;
rep(i,1,n){
if(i == idx) continue;
e[m++] = {i,idx,a[idx]+a[i]};
}
sort(e,e+m,cmp);
rep(i,1,n) fa[i] = i;
LL ans = 0;
for(int u = 0,i = 0;i < m;++i){
int fx = find(e[i].x),fy = find(e[i].y);
if(fx != fy){
fa[fx] = fy;
ans += e[i].w;
if((++u) >= n-1) break;
}
}
printf("%lld\\n",ans);
return 0;
}
更为复杂的解法
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
const int SZ = 2e5 + 5;
int n,m;LL a[SZ];int fa[SZ];LL siz[SZ];
struct X{
int u;LL v;
};
struct Xcmp{
bool operator () (const X &a,const X &b) const{
if(a.v ^ b.v) return a.v < b.v;
return a.u < b.u;
}
};
struct Edg{
int x,y;LL w;
}e[SZ*2];
bool cmp(Edg x,Edg y){
return x.w < y.w;
}
void dbg(){puts("");}
template<typename T, typename... R>void dbg(const T &f, const R &... r) {
cout << f << " ";
dbg(r...);
}
template<typename Type>inline void read(Type &xx){
Type f = 1;char ch;xx = 0;
for(ch = getchar();ch < '0' || ch > '9';ch = getchar()) if(ch == '-') f = -1;
for(;ch >= '0' && ch <= '9';ch = getchar()) xx = xx * 10 + ch - '0';
xx *= f;
}
int find(int x){return x == fa[x] ? x : fa[x] = find(fa[x]);}
int main(int argc, char** argv) {
read(n);read(m);
rep(i,1,n) read(a[i]);
re_(i,0,m){
read(e[i].x);read(e[i].y);read(e[i].w);
}
sort(e,e+m,cmp);
rep(i,1,n) fa[i] = i,siz[i] = a[i];
multiset<X,Xcmp> st;
rep(i,1,n) st.insert({i,siz[i]});
LL ans = 0;
for(int idx = 0;st.size() > 1;){
if(idx < m && find(e[idx].x) == find(e[idx].y)){
++idx;continue;
}
X u = *st.begin();st.erase(st.begin());
X v = *st.begin();st.erase(st.begin());
if(idx >= m || e[idx].w > u.v+v.v){
int fu = find(u.u),fv = find(v.u);
fa[fu] = fv;
ans += siz[fu] + siz[fv];
siz[fv] = min(siz[fv],siz[fu]);
st.insert({fv,siz[fv]});
}
else{
int fx = find(e[idx].x),fy = find(e[idx].y);
st.insert(u);
st.insert(v);
st.erase(st.find({fx,siz[fx]}));
st.erase(st.find({fy,siz[fy]}));
fa[fx] = fy;
ans += e[idx].w;
siz[fy] = min(siz[fy],siz[fx]);
st.insert({fy,siz[fy]});
++idx;
}
}
printf("%lld\\n",ans);
return 0;
}
以上是关于CF1095F——两种贪心解法的内在一致性;kruskal的主要内容,如果未能解决你的问题,请参考以下文章
CF 1039D You Are Given a Tree && CF1059E Split the Tree 的贪心解法
CF #610Div2 B2.K for the Price of One (Hard Version) (dp解法 && 贪心解法)