libreoj 10069 「一本通 3.1 练习 4」Tree 题解

题意简述

一个图有 $n$ 个点 $m$ 条边,每个边是白色或黑色。生成一棵树使得白边的数量恰好是 $k$,并且边权和最小。输出最小的边权和。

$1\le n,m\le 10^5$,边权在 $[1,100]$ 之间

思路

我们在跑朴素的 Kruskal 的时候,把每个白边都调的贵一点,或者便宜一点。把白边弄便宜的时候,就会多选几个白边,反之就会少选几个白边。

所以我们二分一个附加权值 $x$,可正可负。每个白边的权值都 +=x 。如果这样选出来的白边数量 $\le k$,那么 $x$ 应该更小一点(整便宜点,让我们可以多选几个),否则 $x$ 应该更大一点(同理)。

那么最后的答案就是 $sum-k\times x$。 ($sum$ 为附加值为 $x$ 时的最小生成树)
注意!是 $sum-k\times x$,而不是 $sum-white\times x$ (其中 $white$ 为选出的白边的数量)

代码

点击
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

#include <bits/stdc++.h>
using namespace std;
namespace Flandre_Scarlet
{
    #define N 155555
    #define F(i,l,r) for(int i=l;i<=r;++i)
    #define D(i,r,l) for(int i=r;i>=l;--i)
    #define Fs(i,l,r,c) for(int i=l;i<=r;c)
    #define Ds(i,r,l,c) for(int i=r;i>=l;c)
    #define MEM(x,a) memset(x,a,sizeof(x))
    #define FK(x) MEM(x,0)
    #define Tra(i,u) for(int i=G.Start(u),v=G.To(i);~i;i=G.Next(i),v=G.To(i))
    #define p_b push_back
    #define sz(a) ((int)a.size())
    #define iter(a,p) (a.begin()+p)
    void R1(int &x)
    {
        x=0;char c=getchar();int f=1;
        while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
        while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
        x=(f==1)?x:-x;
    }
    void Rd(int cnt,...)
    {
        va_list args;
        va_start(args,cnt);
        F(i,1,cnt) 
        {
            int* x=va_arg(args,int*);R1(*x);
        }
        va_end(args);
    }
    class DSU //并查集
    {
    public:
        int Fa[N],Cnt[N];
        void Init() {F(i,0,N-1) Fa[i]=i,Cnt[i]=1;}
        int  Find(int u) {return u==Fa[u]?u:Fa[u]=Find(Fa[u]);}
        bool Merge(int u,int v)
        {
            int au=Find(u),av=Find(v);
            if (au==av) return false;
            if (Cnt[au]<Cnt[av]) Cnt[av]+=Cnt[au],Fa[au]=av;
            else                 Cnt[au]+=Cnt[av],Fa[av]=au;
            return true;
        }
    }D;
    struct Edge{int u,v,w,c;}E[N]; //存边
    bool operator<(Edge a,Edge b){return a.w<b.w or (a.w==b.w and a.c<b.c);}
    int n,m,k;
    void Input()
    {
        Rd(3,&n,&m,&k);
        F(i,1,m) {int u,v,w,c; Rd(4,&u,&v,&w,&c); ++u,++v; E[i]=(Edge){u,v,w,c};}
    }
    int sum=0,white=0;
    void MST(int delt) //delt为附加权值
    {
        sum=white=0;
        F(i,1,m) if (E[i].c==0) E[i].w+=delt; //每个白边权值都加上delt
        D.Init(); 
        sort(E+1,E+m+1);
        int cnt=0;
        F(i,1,m) if (D.Find(E[i].u)!=D.Find(E[i].v))
        {
            D.Merge(E[i].u,E[i].v);
            sum+=E[i].w;
            white+=(E[i].c==0);
            ++cnt;
            if (cnt==n-1break;
        }
        F(i,1,m) if (E[i].c==0) E[i].w-=delt; //记得改回来
    }
    void Soviet()
    {
        int l=-1000,r=1000;
        int ans;
        while(l<=r)
        {
            int mid=(l+r)>>1; MST(mid);
            if (white>=k) ans=sum-k*mid,l=mid+1;
//答案是sum-k*mid,而不是sum-white*mid
            else r=mid-1;
        }
        printf("%d\n",ans);
    }
    #define Flan void
    Flan IsMyWife()
    {
        Input();
        Soviet();
    }
}
int main()
{
    Flandre_Scarlet::IsMyWife();
    getchar();getchar();
    return 0;
}
w