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的主要内容,如果未能解决你的问题,请参考以下文章

[CF1095F]Make It Connected

CF 1039D You Are Given a Tree && CF1059E Split the Tree 的贪心解法

CF #610Div2 B2.K for the Price of One (Hard Version) (dp解法 && 贪心解法)

CF981D

CF1567C——两种解法:隔离的思想状压dp;并拓展到一般情况

Solution: 题解 CF1059E Split the Tree