题目链接

推荐FlashHu的博客,用导数来理解\(wqs\)二分真的太妙啦,也解释为啥必须是凸函数才能用\(wqs\)二分

还有Creeper_LKF的数形结合的博客

Solution Tree I

\(wqs\)二分


分析:设\(g(x)\)为恰好选\(x\)条白边的最小生成树权值和,那么经过我们可以证明出\(g\)是一个下凸函数(其实可以\(dp\)的……)

然后我们就可以用\(wqs\)二分了

这玩意儿通常用来解决有某种限制,每个物品有个权值,钦定必须选\(x\)个时候的最大/小权值和

要求函数\(g\)必须是凸函数,这样我们才可以对导函数进行二分

以上凸函数为例,我们要求的是\(g\)在给定的\(m\)处的取值,显然我们没法直接算出来(废话,不然干嘛不直接算,雾)

但是我们可以通过贪心 / \(dp\)等等来求出不考虑限制的时候的最大函数值,顺带求出自变量的值。然后我们看这个函数\(g\)的导函数\(g'\),当\(g\)取到最大值的时候\(g'\)\(x\)

如果我们把\(g(x)\)加上\(kx\)的话,不就等价于把导函数向上平移\(k\)个单位了吗

我们二分\(k\),使得导函数交\(x\)轴于我们期望的\(m\),然后这个时候我们就可以求出\(g(m)\)

完了?

我们加上了\(kx\),也就是对\(g(m)\)加上了\(km\),记得减掉

此题同理,我们二分一个\(k\),对所有白边的权值加上\(k\),跑\(Kruskal\)即可

黑白边同权选白边,然后没必要每次都做快排,可以黑白边分开然后每次跑归并排序,这样可以省下一个\(log\)的复杂度

#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 100;
inline int read(){
    int x = 0;char c = getchar();
    while(!isdigit(c))c = getchar();
    while(isdigit(c))x = x * 10 + c - '0',c = getchar();
    return x;
}
struct Edge{
    int from,to,dist,col;
}Edges[maxn],black[maxn],white[maxn];
int f[maxn],n,m,need,sz,black_siz,white_siz;
inline void init(){
    for(int i = 1;i <= n;i++)f[i] = i;
    sz = n;
}
inline int find(int x){
    return (x == f[x]) ? x : f[x] = find(f[x]);
}
inline void merge(int a,int b){
    int x = find(a),y = find(b);
    if(x != y){
        sz--;
        f[x] = y;
    }
}
inline int kruskal(int delta){
    int res = 0,posa = 1,posb = 1,tot = 0;
    while(posa <= white_siz && posb <= black_siz)
        if(white[posa].dist + delta <= black[posb].dist)Edges[++tot] = white[posa++],Edges[tot].dist += delta;
        else Edges[++tot] = black[posb++];
    while(posa <= white_siz)Edges[++tot] = white[posa++],Edges[tot].dist += delta;;
    while(posb <= black_siz)Edges[++tot] = black[posb++];
    init();
    for(int i = 1;i <= m && sz != 1;i++){
        const Edge &e = Edges[i];
        if(find(e.from) != find(e.to))res += !e.col;
        merge(e.from,e.to);
    }
    return res;
}
int main(){
    n = read(),m = read(),need = read();
    for(int u,v,w,c,i = 1;i <= m;i++){
        u = read() + 1,v = read() + 1,w = read(),c = read();
        if(c == 0)white[++white_siz] = Edge{u,v,w,c};
        else black[++black_siz] = Edge{u,v,w,c};
    }
    sort(white + 1,white + 1 + white_siz,[](const Edge &a,const Edge &b){return a.dist < b.dist;});
    sort(black + 1,black + 1 + black_siz,[](const Edge &a,const Edge &b){return a.dist < b.dist;});
    int l = -111,r = 111,ans = 233;
    while(l <= r){
        int mid = (l + r) >> 1,tmp = kruskal(mid);
        if(tmp < need)r = mid - 1;
        else ans = mid,l = mid + 1;
    }
    if(ans == 233)abort();
    int posa = 1,posb = 1,tot = 0;
    while(posa <= white_siz && posb <= black_siz)
        if(white[posa].dist + ans <= black[posb].dist)Edges[++tot] = white[posa++],Edges[tot].dist += ans;
        else Edges[++tot] = black[posb++];
    while(posa <= white_siz)Edges[++tot] = white[posa++],Edges[tot].dist += ans;
    while(posb <= black_siz)Edges[++tot] = black[posb++];
    init();
    ans *= -need;
    for(int i = 1;i <= m && sz != 1;i++){
        const Edge &e = Edges[i];
        if(find(e.from) != find(e.to))ans += e.dist;
        merge(e.from,e.to);
    }
    printf("%d\n",ans);
    return 0;
}
01-14 23:36
查看更多